Showing
5 changed files
with
1294 additions
and
1111 deletions
... | @@ -70,6 +70,7 @@ | ... | @@ -70,6 +70,7 @@ |
70 | <script type="text/javascript"> | 70 | <script type="text/javascript"> |
71 | var ONOS = $.onos({ | 71 | var ONOS = $.onos({ |
72 | comment: "configuration options", | 72 | comment: "configuration options", |
73 | + startVid: 'topo', | ||
73 | trace: false | 74 | trace: false |
74 | }); | 75 | }); |
75 | </script> | 76 | </script> |
... | @@ -77,12 +78,15 @@ | ... | @@ -77,12 +78,15 @@ |
77 | <!-- Framework module files included here --> | 78 | <!-- Framework module files included here --> |
78 | <script src="mast2.js"></script> | 79 | <script src="mast2.js"></script> |
79 | 80 | ||
80 | - <!-- Contributed (application) views injected here --> | 81 | + <!-- Sample views; can be dispensed with eventually --> |
81 | - <!-- TODO: replace with template marker and inject refs server-side --> | ||
82 | <script src="sample2.js"></script> | 82 | <script src="sample2.js"></script> |
83 | <script src="sampleAlt2.js"></script> | 83 | <script src="sampleAlt2.js"></script> |
84 | <script src="sampleRadio.js"></script> | 84 | <script src="sampleRadio.js"></script> |
85 | 85 | ||
86 | + <!-- Contributed (application) views injected here --> | ||
87 | + <!-- TODO: replace with template marker and inject refs server-side --> | ||
88 | + <script src="topo2.js"></script> | ||
89 | + | ||
86 | <!-- finally, build the UI--> | 90 | <!-- finally, build the UI--> |
87 | <script type="text/javascript"> | 91 | <script type="text/javascript"> |
88 | $(ONOS.buildUi); | 92 | $(ONOS.buildUi); | ... | ... |
... | @@ -25,7 +25,7 @@ | ... | @@ -25,7 +25,7 @@ |
25 | var tsI = new Date().getTime(), // initialize time stamp | 25 | var tsI = new Date().getTime(), // initialize time stamp |
26 | tsB, // build time stamp | 26 | tsB, // build time stamp |
27 | mastHeight = 36, // see mast2.css | 27 | mastHeight = 36, // see mast2.css |
28 | - defaultHash = 'sample'; | 28 | + defaultVid = 'sample'; |
29 | 29 | ||
30 | 30 | ||
31 | // attach our main function to the jQuery object | 31 | // attach our main function to the jQuery object |
... | @@ -35,7 +35,8 @@ | ... | @@ -35,7 +35,8 @@ |
35 | navApi; | 35 | navApi; |
36 | 36 | ||
37 | var defaultOptions = { | 37 | var defaultOptions = { |
38 | - trace: false | 38 | + trace: false, |
39 | + startVid: defaultVid | ||
39 | }; | 40 | }; |
40 | 41 | ||
41 | // compute runtime settings | 42 | // compute runtime settings |
... | @@ -91,7 +92,7 @@ | ... | @@ -91,7 +92,7 @@ |
91 | traceFn('hash', hash); | 92 | traceFn('hash', hash); |
92 | 93 | ||
93 | if (!hash) { | 94 | if (!hash) { |
94 | - hash = defaultHash; | 95 | + hash = settings.startVid; |
95 | redo = true; | 96 | redo = true; |
96 | } | 97 | } |
97 | 98 | ||
... | @@ -336,10 +337,6 @@ | ... | @@ -336,10 +337,6 @@ |
336 | } | 337 | } |
337 | 338 | ||
338 | var viewInstanceMethods = { | 339 | var viewInstanceMethods = { |
339 | - toString: function () { | ||
340 | - return '[View: id="' + this.vid + '"]'; | ||
341 | - }, | ||
342 | - | ||
343 | token: function () { | 340 | token: function () { |
344 | return { | 341 | return { |
345 | // attributes | 342 | // attributes |
... | @@ -350,6 +347,7 @@ | ... | @@ -350,6 +347,7 @@ |
350 | // functions | 347 | // functions |
351 | width: this.width, | 348 | width: this.width, |
352 | height: this.height, | 349 | height: this.height, |
350 | + uid: this.uid, | ||
353 | setRadio: this.setRadio | 351 | setRadio: this.setRadio |
354 | } | 352 | } |
355 | }, | 353 | }, |
... | @@ -433,6 +431,10 @@ | ... | @@ -433,6 +431,10 @@ |
433 | 431 | ||
434 | setRadio: function (btnSet, cb) { | 432 | setRadio: function (btnSet, cb) { |
435 | setRadioButtons(this.vid, btnSet, cb); | 433 | setRadioButtons(this.vid, btnSet, cb); |
434 | + }, | ||
435 | + | ||
436 | + uid: function (id) { | ||
437 | + return uid(this, id); | ||
436 | } | 438 | } |
437 | 439 | ||
438 | // TODO: consider schedule, clearTimer, etc. | 440 | // TODO: consider schedule, clearTimer, etc. | ... | ... |
web/gui/src/main/webapp/topo2-OLD.js
0 → 100644
1 | +/* | ||
2 | + * Copyright 2014 Open Networking Laboratory | ||
3 | + * | ||
4 | + * 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 | ||
7 | + * | ||
8 | + * 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. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS network topology viewer - PoC version 1.0 | ||
19 | + | ||
20 | + @author Simon Hunt | ||
21 | + */ | ||
22 | + | ||
23 | +(function (onos) { | ||
24 | + 'use strict'; | ||
25 | + | ||
26 | + // configuration data | ||
27 | + var config = { | ||
28 | + useLiveData: true, | ||
29 | + debugOn: false, | ||
30 | + debug: { | ||
31 | + showNodeXY: false, | ||
32 | + showKeyHandler: true | ||
33 | + }, | ||
34 | + options: { | ||
35 | + layering: true, | ||
36 | + collisionPrevention: true, | ||
37 | + loadBackground: true | ||
38 | + }, | ||
39 | + backgroundUrl: 'img/us-map.png', | ||
40 | + data: { | ||
41 | + live: { | ||
42 | + jsonUrl: 'rs/topology/graph', | ||
43 | + detailPrefix: 'rs/topology/graph/', | ||
44 | + detailSuffix: '' | ||
45 | + }, | ||
46 | + fake: { | ||
47 | + jsonUrl: 'json/network2.json', | ||
48 | + detailPrefix: 'json/', | ||
49 | + detailSuffix: '.json' | ||
50 | + } | ||
51 | + }, | ||
52 | + iconUrl: { | ||
53 | + device: 'img/device.png', | ||
54 | + host: 'img/host.png', | ||
55 | + pkt: 'img/pkt.png', | ||
56 | + opt: 'img/opt.png' | ||
57 | + }, | ||
58 | + mastHeight: 36, | ||
59 | + force: { | ||
60 | + note: 'node.class or link.class is used to differentiate', | ||
61 | + linkDistance: { | ||
62 | + infra: 200, | ||
63 | + host: 40 | ||
64 | + }, | ||
65 | + linkStrength: { | ||
66 | + infra: 1.0, | ||
67 | + host: 1.0 | ||
68 | + }, | ||
69 | + charge: { | ||
70 | + device: -800, | ||
71 | + host: -1000 | ||
72 | + }, | ||
73 | + ticksWithoutCollisions: 50, | ||
74 | + marginLR: 20, | ||
75 | + marginTB: 20, | ||
76 | + translate: function() { | ||
77 | + return 'translate(' + | ||
78 | + config.force.marginLR + ',' + | ||
79 | + config.force.marginTB + ')'; | ||
80 | + } | ||
81 | + }, | ||
82 | + labels: { | ||
83 | + imgPad: 16, | ||
84 | + padLR: 8, | ||
85 | + padTB: 6, | ||
86 | + marginLR: 3, | ||
87 | + marginTB: 2, | ||
88 | + port: { | ||
89 | + gap: 3, | ||
90 | + width: 18, | ||
91 | + height: 14 | ||
92 | + } | ||
93 | + }, | ||
94 | + icons: { | ||
95 | + w: 32, | ||
96 | + h: 32, | ||
97 | + xoff: -12, | ||
98 | + yoff: -8 | ||
99 | + }, | ||
100 | + constraints: { | ||
101 | + ypos: { | ||
102 | + host: 0.05, | ||
103 | + switch: 0.3, | ||
104 | + roadm: 0.7 | ||
105 | + } | ||
106 | + }, | ||
107 | + hostLinkWidth: 1.0, | ||
108 | + hostRadius: 7, | ||
109 | + mouseOutTimerDelayMs: 120 | ||
110 | + }; | ||
111 | + | ||
112 | + // state variables | ||
113 | + var netView = {}, | ||
114 | + network = {}, | ||
115 | + selected = {}, | ||
116 | + highlighted = null, | ||
117 | + hovered = null, | ||
118 | + viewMode = 'showAll', | ||
119 | + portLabelsOn = false; | ||
120 | + | ||
121 | + | ||
122 | + function debug(what) { | ||
123 | + return config.debugOn && config.debug[what]; | ||
124 | + } | ||
125 | + | ||
126 | + function urlData() { | ||
127 | + return config.data[config.useLiveData ? 'live' : 'fake']; | ||
128 | + } | ||
129 | + | ||
130 | + function networkJsonUrl() { | ||
131 | + return urlData().jsonUrl; | ||
132 | + } | ||
133 | + | ||
134 | + function safeId(id) { | ||
135 | + return id.replace(/[^a-z0-9]/gi, '_'); | ||
136 | + } | ||
137 | + | ||
138 | + function detailJsonUrl(id) { | ||
139 | + var u = urlData(), | ||
140 | + encId = config.useLiveData ? encodeURIComponent(id) : safeId(id); | ||
141 | + return u.detailPrefix + encId + u.detailSuffix; | ||
142 | + } | ||
143 | + | ||
144 | + | ||
145 | + // load the topology view of the network | ||
146 | + function loadNetworkView() { | ||
147 | + // Hey, here I am, calling something on the ONOS api: | ||
148 | + api.printTime(); | ||
149 | + | ||
150 | + resize(); | ||
151 | + | ||
152 | + // go get our network data from the server... | ||
153 | + var url = networkJsonUrl(); | ||
154 | + d3.json(url , function (err, data) { | ||
155 | + if (err) { | ||
156 | + alert('Oops! Error reading JSON...\n\n' + | ||
157 | + 'URL: ' + url + '\n\n' + | ||
158 | + 'Error: ' + err.message); | ||
159 | + return; | ||
160 | + } | ||
161 | +// console.log("here is the JSON data..."); | ||
162 | +// console.log(data); | ||
163 | + | ||
164 | + network.data = data; | ||
165 | + drawNetwork(); | ||
166 | + }); | ||
167 | + | ||
168 | + // while we wait for the data, set up the handlers... | ||
169 | + setUpClickHandler(); | ||
170 | + setUpRadioButtonHandler(); | ||
171 | + setUpKeyHandler(); | ||
172 | + $(window).on('resize', resize); | ||
173 | + } | ||
174 | + | ||
175 | + function setUpClickHandler() { | ||
176 | + // click handler for "selectable" objects | ||
177 | + $(document).on('click', '.select-object', function () { | ||
178 | + // when any object of class "select-object" is clicked... | ||
179 | + var obj = network.lookup[$(this).data('id')]; | ||
180 | + if (obj) { | ||
181 | + selectObject(obj); | ||
182 | + } | ||
183 | + // stop propagation of event (I think) ... | ||
184 | + return false; | ||
185 | + }); | ||
186 | + } | ||
187 | + | ||
188 | + function setUpRadioButtonHandler() { | ||
189 | + d3.selectAll('#displayModes .radio').on('click', function () { | ||
190 | + var id = d3.select(this).attr('id'); | ||
191 | + if (id !== viewMode) { | ||
192 | + radioButton('displayModes', id); | ||
193 | + viewMode = id; | ||
194 | + doRadioAction(id); | ||
195 | + } | ||
196 | + }); | ||
197 | + } | ||
198 | + | ||
199 | + function doRadioAction(id) { | ||
200 | + showAllLayers(); | ||
201 | + if (id === 'showPkt') { | ||
202 | + showPacketLayer(); | ||
203 | + } else if (id === 'showOpt') { | ||
204 | + showOpticalLayer(); | ||
205 | + } | ||
206 | + } | ||
207 | + | ||
208 | + function showAllLayers() { | ||
209 | + network.node.classed('inactive', false); | ||
210 | + network.link.classed('inactive', false); | ||
211 | + d3.selectAll('svg .port').classed('inactive', false); | ||
212 | + d3.selectAll('svg .portText').classed('inactive', false); | ||
213 | + } | ||
214 | + | ||
215 | + function showPacketLayer() { | ||
216 | + network.node.each(function(d) { | ||
217 | + // deactivate nodes that are not hosts or switches | ||
218 | + if (d.class === 'device' && d.type !== 'switch') { | ||
219 | + d3.select(this).classed('inactive', true); | ||
220 | + } | ||
221 | + }); | ||
222 | + | ||
223 | + network.link.each(function(lnk) { | ||
224 | + // deactivate infrastructure links that have opt's as endpoints | ||
225 | + if (lnk.source.type === 'roadm' || lnk.target.type === 'roadm') { | ||
226 | + d3.select(this).classed('inactive', true); | ||
227 | + } | ||
228 | + }); | ||
229 | + | ||
230 | + // deactivate non-packet ports | ||
231 | + d3.selectAll('svg .optPort').classed('inactive', true) | ||
232 | + } | ||
233 | + | ||
234 | + function showOpticalLayer() { | ||
235 | + network.node.each(function(d) { | ||
236 | + // deactivate nodes that are not optical devices | ||
237 | + if (d.type !== 'roadm') { | ||
238 | + d3.select(this).classed('inactive', true); | ||
239 | + } | ||
240 | + }); | ||
241 | + | ||
242 | + network.link.each(function(lnk) { | ||
243 | + // deactivate infrastructure links that have opt's as endpoints | ||
244 | + if (lnk.source.type !== 'roadm' || lnk.target.type !== 'roadm') { | ||
245 | + d3.select(this).classed('inactive', true); | ||
246 | + } | ||
247 | + }); | ||
248 | + | ||
249 | + // deactivate non-packet ports | ||
250 | + d3.selectAll('svg .pktPort').classed('inactive', true) | ||
251 | + } | ||
252 | + | ||
253 | + function setUpKeyHandler() { | ||
254 | + d3.select('body') | ||
255 | + .on('keydown', function () { | ||
256 | + processKeyEvent(); | ||
257 | + if (debug('showKeyHandler')) { | ||
258 | + network.svg.append('text') | ||
259 | + .attr('x', 5) | ||
260 | + .attr('y', 15) | ||
261 | + .style('font-size', '20pt') | ||
262 | + .text('keyCode: ' + d3.event.keyCode + | ||
263 | + ' applied to : ' + contextLabel()) | ||
264 | + .transition().duration(2000) | ||
265 | + .style('font-size', '2pt') | ||
266 | + .style('fill-opacity', 0.01) | ||
267 | + .remove(); | ||
268 | + } | ||
269 | + }); | ||
270 | + } | ||
271 | + | ||
272 | + function contextLabel() { | ||
273 | + return hovered === null ? "(nothing)" : hovered.id; | ||
274 | + } | ||
275 | + | ||
276 | + function radioButton(group, id) { | ||
277 | + d3.selectAll("#" + group + " .radio").classed("active", false); | ||
278 | + d3.select("#" + group + " #" + id).classed("active", true); | ||
279 | + } | ||
280 | + | ||
281 | + function processKeyEvent() { | ||
282 | + var code = d3.event.keyCode; | ||
283 | + switch (code) { | ||
284 | + case 66: // B | ||
285 | + toggleBackground(); | ||
286 | + break; | ||
287 | + case 71: // G | ||
288 | + cycleLayout(); | ||
289 | + break; | ||
290 | + case 76: // L | ||
291 | + cycleLabels(); | ||
292 | + break; | ||
293 | + case 80: // P | ||
294 | + togglePorts(); | ||
295 | + break; | ||
296 | + case 85: // U | ||
297 | + unpin(); | ||
298 | + break; | ||
299 | + } | ||
300 | + | ||
301 | + } | ||
302 | + | ||
303 | + function toggleBackground() { | ||
304 | + var bg = d3.select('#bg'), | ||
305 | + vis = bg.style('visibility'), | ||
306 | + newvis = (vis === 'hidden') ? 'visible' : 'hidden'; | ||
307 | + bg.style('visibility', newvis); | ||
308 | + } | ||
309 | + | ||
310 | + function cycleLayout() { | ||
311 | + config.options.layering = !config.options.layering; | ||
312 | + network.force.resume(); | ||
313 | + } | ||
314 | + | ||
315 | + function cycleLabels() { | ||
316 | + console.log('Cycle Labels - context = ' + contextLabel()); | ||
317 | + } | ||
318 | + | ||
319 | + function togglePorts() { | ||
320 | + portLabelsOn = !portLabelsOn; | ||
321 | + var portVis = portLabelsOn ? 'visible' : 'hidden'; | ||
322 | + d3.selectAll('.port').style('visibility', portVis); | ||
323 | + d3.selectAll('.portText').style('visibility', portVis); | ||
324 | + } | ||
325 | + | ||
326 | + function unpin() { | ||
327 | + if (hovered) { | ||
328 | + hovered.fixed = false; | ||
329 | + findNodeFromData(hovered).classed('fixed', false); | ||
330 | + network.force.resume(); | ||
331 | + } | ||
332 | + console.log('Unpin - context = ' + contextLabel()); | ||
333 | + } | ||
334 | + | ||
335 | + | ||
336 | + // ======================================================== | ||
337 | + | ||
338 | + function drawNetwork() { | ||
339 | + $('#view').empty(); | ||
340 | + | ||
341 | + prepareNodesAndLinks(); | ||
342 | + createLayout(); | ||
343 | + console.log("\n\nHere is the augmented network object..."); | ||
344 | + console.log(network); | ||
345 | + } | ||
346 | + | ||
347 | + function prepareNodesAndLinks() { | ||
348 | + network.lookup = {}; | ||
349 | + network.nodes = []; | ||
350 | + network.links = []; | ||
351 | + | ||
352 | + var nw = network.forceWidth, | ||
353 | + nh = network.forceHeight; | ||
354 | + | ||
355 | + function yPosConstraintForNode(n) { | ||
356 | + return config.constraints.ypos[n.type || 'host']; | ||
357 | + } | ||
358 | + | ||
359 | + // Note that both 'devices' and 'hosts' get mapped into the nodes array | ||
360 | + | ||
361 | + // first, the devices... | ||
362 | + network.data.devices.forEach(function(n) { | ||
363 | + var ypc = yPosConstraintForNode(n), | ||
364 | + ix = Math.random() * 0.6 * nw + 0.2 * nw, | ||
365 | + iy = ypc * nh, | ||
366 | + node = { | ||
367 | + id: n.id, | ||
368 | + labels: n.labels, | ||
369 | + class: 'device', | ||
370 | + icon: 'device', | ||
371 | + type: n.type, | ||
372 | + x: ix, | ||
373 | + y: iy, | ||
374 | + constraint: { | ||
375 | + weight: 0.7, | ||
376 | + y: iy | ||
377 | + } | ||
378 | + }; | ||
379 | + network.lookup[n.id] = node; | ||
380 | + network.nodes.push(node); | ||
381 | + }); | ||
382 | + | ||
383 | + // then, the hosts... | ||
384 | + network.data.hosts.forEach(function(n) { | ||
385 | + var ypc = yPosConstraintForNode(n), | ||
386 | + ix = Math.random() * 0.6 * nw + 0.2 * nw, | ||
387 | + iy = ypc * nh, | ||
388 | + node = { | ||
389 | + id: n.id, | ||
390 | + labels: n.labels, | ||
391 | + class: 'host', | ||
392 | + icon: 'host', | ||
393 | + type: n.type, | ||
394 | + x: ix, | ||
395 | + y: iy, | ||
396 | + constraint: { | ||
397 | + weight: 0.7, | ||
398 | + y: iy | ||
399 | + } | ||
400 | + }; | ||
401 | + network.lookup[n.id] = node; | ||
402 | + network.nodes.push(node); | ||
403 | + }); | ||
404 | + | ||
405 | + | ||
406 | + // now, process the explicit links... | ||
407 | + network.data.links.forEach(function(lnk) { | ||
408 | + var src = network.lookup[lnk.src], | ||
409 | + dst = network.lookup[lnk.dst], | ||
410 | + id = src.id + "-" + dst.id; | ||
411 | + | ||
412 | + var link = { | ||
413 | + class: 'infra', | ||
414 | + id: id, | ||
415 | + type: lnk.type, | ||
416 | + width: lnk.linkWidth, | ||
417 | + source: src, | ||
418 | + srcPort: lnk.srcPort, | ||
419 | + target: dst, | ||
420 | + tgtPort: lnk.dstPort, | ||
421 | + strength: config.force.linkStrength.infra | ||
422 | + }; | ||
423 | + network.links.push(link); | ||
424 | + }); | ||
425 | + | ||
426 | + // finally, infer host links... | ||
427 | + network.data.hosts.forEach(function(n) { | ||
428 | + var src = network.lookup[n.id], | ||
429 | + dst = network.lookup[n.cp.device], | ||
430 | + id = src.id + "-" + dst.id; | ||
431 | + | ||
432 | + var link = { | ||
433 | + class: 'host', | ||
434 | + id: id, | ||
435 | + type: 'hostLink', | ||
436 | + width: config.hostLinkWidth, | ||
437 | + source: src, | ||
438 | + target: dst, | ||
439 | + strength: config.force.linkStrength.host | ||
440 | + }; | ||
441 | + network.links.push(link); | ||
442 | + }); | ||
443 | + } | ||
444 | + | ||
445 | + function createLayout() { | ||
446 | + | ||
447 | + var cfg = config.force; | ||
448 | + | ||
449 | + network.force = d3.layout.force() | ||
450 | + .size([network.forceWidth, network.forceHeight]) | ||
451 | + .nodes(network.nodes) | ||
452 | + .links(network.links) | ||
453 | + .linkStrength(function(d) { return cfg.linkStrength[d.class]; }) | ||
454 | + .linkDistance(function(d) { return cfg.linkDistance[d.class]; }) | ||
455 | + .charge(function(d) { return cfg.charge[d.class]; }) | ||
456 | + .on('tick', tick); | ||
457 | + | ||
458 | + network.svg = d3.select('#view').append('svg') | ||
459 | + .attr('width', netView.width) | ||
460 | + .attr('height', netView.height) | ||
461 | + .append('g') | ||
462 | + .attr('transform', config.force.translate()); | ||
463 | +// .attr('id', 'zoomable') | ||
464 | +// .call(d3.behavior.zoom().on("zoom", zoomRedraw)); | ||
465 | + | ||
466 | + network.svg.append('svg:image') | ||
467 | + .attr({ | ||
468 | + id: 'bg', | ||
469 | + width: netView.width, | ||
470 | + height: netView.height, | ||
471 | + 'xlink:href': config.backgroundUrl | ||
472 | + }) | ||
473 | + .style('visibility', | ||
474 | + config.options.loadBackground ? 'visible' : 'hidden'); | ||
475 | + | ||
476 | +// function zoomRedraw() { | ||
477 | +// d3.select("#zoomable").attr("transform", | ||
478 | +// "translate(" + d3.event.translate + ")" | ||
479 | +// + " scale(" + d3.event.scale + ")"); | ||
480 | +// } | ||
481 | + | ||
482 | + // TODO: move glow/blur stuff to util script | ||
483 | + var glow = network.svg.append('filter') | ||
484 | + .attr('x', '-50%') | ||
485 | + .attr('y', '-50%') | ||
486 | + .attr('width', '200%') | ||
487 | + .attr('height', '200%') | ||
488 | + .attr('id', 'blue-glow'); | ||
489 | + | ||
490 | + glow.append('feColorMatrix') | ||
491 | + .attr('type', 'matrix') | ||
492 | + .attr('values', '0 0 0 0 0 ' + | ||
493 | + '0 0 0 0 0 ' + | ||
494 | + '0 0 0 0 .7 ' + | ||
495 | + '0 0 0 1 0 '); | ||
496 | + | ||
497 | + glow.append('feGaussianBlur') | ||
498 | + .attr('stdDeviation', 3) | ||
499 | + .attr('result', 'coloredBlur'); | ||
500 | + | ||
501 | + glow.append('feMerge').selectAll('feMergeNode') | ||
502 | + .data(['coloredBlur', 'SourceGraphic']) | ||
503 | + .enter().append('feMergeNode') | ||
504 | + .attr('in', String); | ||
505 | + | ||
506 | + // TODO: legend (and auto adjust on scroll) | ||
507 | +// $('#view').on('scroll', function() { | ||
508 | +// | ||
509 | +// }); | ||
510 | + | ||
511 | + | ||
512 | + // TODO: move drag behavior into separate method. | ||
513 | + // == define node drag behavior... | ||
514 | + network.draggedThreshold = d3.scale.linear() | ||
515 | + .domain([0, 0.1]) | ||
516 | + .range([5, 20]) | ||
517 | + .clamp(true); | ||
518 | + | ||
519 | + function dragged(d) { | ||
520 | + var threshold = network.draggedThreshold(network.force.alpha()), | ||
521 | + dx = d.oldX - d.px, | ||
522 | + dy = d.oldY - d.py; | ||
523 | + if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) { | ||
524 | + d.dragged = true; | ||
525 | + } | ||
526 | + return d.dragged; | ||
527 | + } | ||
528 | + | ||
529 | + network.drag = d3.behavior.drag() | ||
530 | + .origin(function(d) { return d; }) | ||
531 | + .on('dragstart', function(d) { | ||
532 | + d.oldX = d.x; | ||
533 | + d.oldY = d.y; | ||
534 | + d.dragged = false; | ||
535 | + d.fixed |= 2; | ||
536 | + }) | ||
537 | + .on('drag', function(d) { | ||
538 | + d.px = d3.event.x; | ||
539 | + d.py = d3.event.y; | ||
540 | + if (dragged(d)) { | ||
541 | + if (!network.force.alpha()) { | ||
542 | + network.force.alpha(.025); | ||
543 | + } | ||
544 | + } | ||
545 | + }) | ||
546 | + .on('dragend', function(d) { | ||
547 | + if (!dragged(d)) { | ||
548 | + selectObject(d, this); | ||
549 | + } | ||
550 | + d.fixed &= ~6; | ||
551 | + | ||
552 | + // once we've finished moving, pin the node in position, | ||
553 | + // if it is a device (not a host) | ||
554 | + if (d.class === 'device') { | ||
555 | + d.fixed = true; | ||
556 | + d3.select(this).classed('fixed', true) | ||
557 | + } | ||
558 | + }); | ||
559 | + | ||
560 | + $('#view').on('click', function(e) { | ||
561 | + if (!$(e.target).closest('.node').length) { | ||
562 | + deselectObject(); | ||
563 | + } | ||
564 | + }); | ||
565 | + | ||
566 | + // ............................................................... | ||
567 | + | ||
568 | + // add links to the display | ||
569 | + network.link = network.svg.append('g').attr('id', 'links') | ||
570 | + .selectAll('.link') | ||
571 | + .data(network.force.links(), function(d) {return d.id}) | ||
572 | + .enter().append('line') | ||
573 | + .attr('class', function(d) {return 'link ' + d.class}); | ||
574 | + | ||
575 | + network.linkSrcPort = network.svg.append('g') | ||
576 | + .attr({ | ||
577 | + id: 'srcPorts', | ||
578 | + class: 'portLayer' | ||
579 | + }); | ||
580 | + network.linkTgtPort = network.svg.append('g') | ||
581 | + .attr({ | ||
582 | + id: 'tgtPorts', | ||
583 | + class: 'portLayer' | ||
584 | + }); | ||
585 | + | ||
586 | + var portVis = portLabelsOn ? 'visible' : 'hidden', | ||
587 | + pw = config.labels.port.width, | ||
588 | + ph = config.labels.port.height; | ||
589 | + | ||
590 | + network.link.filter('.infra').each(function(d) { | ||
591 | + var srcType = d.source.type === 'roadm' ? 'optPort' : 'pktPort', | ||
592 | + tgtType = d.target.type === 'roadm' ? 'optPort' : 'pktPort'; | ||
593 | + | ||
594 | + if (d.source.type) | ||
595 | + | ||
596 | + network.linkSrcPort.append('rect').attr({ | ||
597 | + id: 'srcPort-' + safeId(d.id), | ||
598 | + class: 'port ' + srcType, | ||
599 | + width: pw, | ||
600 | + height: ph, | ||
601 | + rx: 4, | ||
602 | + ry: 4 | ||
603 | + }).style('visibility', portVis); | ||
604 | + | ||
605 | + network.linkTgtPort.append('rect').attr({ | ||
606 | + id: 'tgtPort-' + safeId(d.id), | ||
607 | + class: 'port ' + tgtType, | ||
608 | + width: pw, | ||
609 | + height: ph, | ||
610 | + rx: 4, | ||
611 | + ry: 4 | ||
612 | + }).style('visibility', portVis); | ||
613 | + | ||
614 | + network.linkSrcPort.append('text').attr({ | ||
615 | + id: 'srcText-' + safeId(d.id), | ||
616 | + class: 'portText ' + srcType | ||
617 | + }).text(d.srcPort) | ||
618 | + .style('visibility', portVis); | ||
619 | + | ||
620 | + network.linkTgtPort.append('text').attr({ | ||
621 | + id: 'tgtText-' + safeId(d.id), | ||
622 | + class: 'portText ' + tgtType | ||
623 | + }).text(d.tgtPort) | ||
624 | + .style('visibility', portVis); | ||
625 | + }); | ||
626 | + | ||
627 | + // ............................................................... | ||
628 | + | ||
629 | + // add nodes to the display | ||
630 | + network.node = network.svg.selectAll('.node') | ||
631 | + .data(network.force.nodes(), function(d) {return d.id}) | ||
632 | + .enter().append('g') | ||
633 | + .attr('class', function(d) { | ||
634 | + var cls = 'node ' + d.class; | ||
635 | + if (d.type) { | ||
636 | + cls += ' ' + d.type; | ||
637 | + } | ||
638 | + return cls; | ||
639 | + }) | ||
640 | + .attr('transform', function(d) { | ||
641 | + return translate(d.x, d.y); | ||
642 | + }) | ||
643 | + .call(network.drag) | ||
644 | + .on('mouseover', function(d) { | ||
645 | + // TODO: show tooltip | ||
646 | + if (network.mouseoutTimeout) { | ||
647 | + clearTimeout(network.mouseoutTimeout); | ||
648 | + network.mouseoutTimeout = null; | ||
649 | + } | ||
650 | + hoverObject(d); | ||
651 | + }) | ||
652 | + .on('mouseout', function(d) { | ||
653 | + // TODO: hide tooltip | ||
654 | + if (network.mouseoutTimeout) { | ||
655 | + clearTimeout(network.mouseoutTimeout); | ||
656 | + network.mouseoutTimeout = null; | ||
657 | + } | ||
658 | + network.mouseoutTimeout = setTimeout(function() { | ||
659 | + hoverObject(null); | ||
660 | + }, config.mouseOutTimerDelayMs); | ||
661 | + }); | ||
662 | + | ||
663 | + | ||
664 | + // deal with device nodes first | ||
665 | + network.nodeRect = network.node.filter('.device') | ||
666 | + .append('rect') | ||
667 | + .attr({ | ||
668 | + rx: 5, | ||
669 | + ry: 5, | ||
670 | + width: 100, | ||
671 | + height: 12 | ||
672 | + }); | ||
673 | + // note that width/height are adjusted to fit the label text | ||
674 | + // then padded, and space made for the icon. | ||
675 | + | ||
676 | + network.node.filter('.device').each(function(d) { | ||
677 | + var node = d3.select(this), | ||
678 | + icon = iconUrl(d); | ||
679 | + | ||
680 | + node.append('text') | ||
681 | + // TODO: add label cycle behavior | ||
682 | + .text(d.id) | ||
683 | + .attr('dy', '1.1em'); | ||
684 | + | ||
685 | + if (icon) { | ||
686 | + var cfg = config.icons; | ||
687 | + node.append('svg:image') | ||
688 | + .attr({ | ||
689 | + width: cfg.w, | ||
690 | + height: cfg.h, | ||
691 | + 'xlink:href': icon | ||
692 | + }); | ||
693 | + // note, icon relative positioning (x,y) is done after we have | ||
694 | + // adjusted the bounds of the rectangle... | ||
695 | + } | ||
696 | + | ||
697 | + // debug function to show the modelled x,y coordinates of nodes... | ||
698 | + if (debug('showNodeXY')) { | ||
699 | + node.select('rect').attr('fill-opacity', 0.5); | ||
700 | + node.append('circle') | ||
701 | + .attr({ | ||
702 | + class: 'debug', | ||
703 | + cx: 0, | ||
704 | + cy: 0, | ||
705 | + r: '3px' | ||
706 | + }); | ||
707 | + } | ||
708 | + }); | ||
709 | + | ||
710 | + // now process host nodes | ||
711 | + network.nodeCircle = network.node.filter('.host') | ||
712 | + .append('circle') | ||
713 | + .attr({ | ||
714 | + r: config.hostRadius | ||
715 | + }); | ||
716 | + | ||
717 | + network.node.filter('.host').each(function(d) { | ||
718 | + var node = d3.select(this), | ||
719 | + icon = iconUrl(d); | ||
720 | + | ||
721 | + // debug function to show the modelled x,y coordinates of nodes... | ||
722 | + if (debug('showNodeXY')) { | ||
723 | + node.select('circle').attr('fill-opacity', 0.5); | ||
724 | + node.append('circle') | ||
725 | + .attr({ | ||
726 | + class: 'debug', | ||
727 | + cx: 0, | ||
728 | + cy: 0, | ||
729 | + r: '3px' | ||
730 | + }); | ||
731 | + } | ||
732 | + }); | ||
733 | + | ||
734 | + // this function is scheduled to happen soon after the given thread ends | ||
735 | + setTimeout(function() { | ||
736 | + var lab = config.labels, | ||
737 | + portGap = lab.port.gap, | ||
738 | + midW = portGap + lab.port.width/ 2, | ||
739 | + midH = portGap + lab.port.height / 2; | ||
740 | + | ||
741 | + // post process the device nodes, to pad their size to fit the | ||
742 | + // label text and attach the icon to the right location. | ||
743 | + network.node.filter('.device').each(function(d) { | ||
744 | + // for every node, recompute size, padding, etc. so text fits | ||
745 | + var node = d3.select(this), | ||
746 | + text = node.select('text'), | ||
747 | + box = adjustRectToFitText(node); | ||
748 | + | ||
749 | + // now make the computed adjustment | ||
750 | + node.select('rect') | ||
751 | + .attr(box); | ||
752 | + | ||
753 | + node.select('image') | ||
754 | + .attr('x', box.x + config.icons.xoff) | ||
755 | + .attr('y', box.y + config.icons.yoff); | ||
756 | + | ||
757 | + var bounds = boundsFromBox(box), | ||
758 | + portBounds = { | ||
759 | + x1: bounds.x1 - midW, | ||
760 | + x2: bounds.x2 + midW, | ||
761 | + y1: bounds.y1 - midH, | ||
762 | + y2: bounds.y2 + midH | ||
763 | + }; | ||
764 | + | ||
765 | + // todo: clean up extent and edge work.. | ||
766 | + d.extent = { | ||
767 | + left: bounds.x1 - lab.marginLR, | ||
768 | + right: bounds.x2 + lab.marginLR, | ||
769 | + top: bounds.y1 - lab.marginTB, | ||
770 | + bottom: bounds.y2 + lab.marginTB | ||
771 | + }; | ||
772 | + | ||
773 | + d.edge = { | ||
774 | + left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2), | ||
775 | + right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2), | ||
776 | + top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1), | ||
777 | + bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) | ||
778 | + }; | ||
779 | + | ||
780 | + d.portEdge = { | ||
781 | + left : new geo.LineSegment( | ||
782 | + portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2 | ||
783 | + ), | ||
784 | + right : new geo.LineSegment( | ||
785 | + portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2 | ||
786 | + ), | ||
787 | + top : new geo.LineSegment( | ||
788 | + portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1 | ||
789 | + ), | ||
790 | + bottom : new geo.LineSegment( | ||
791 | + portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2 | ||
792 | + ) | ||
793 | + }; | ||
794 | + | ||
795 | + }); | ||
796 | + | ||
797 | + network.numTicks = 0; | ||
798 | + network.preventCollisions = false; | ||
799 | + network.force.start(); | ||
800 | + for (var i = 0; i < config.force.ticksWithoutCollisions; i++) { | ||
801 | + network.force.tick(); | ||
802 | + } | ||
803 | + network.preventCollisions = true; | ||
804 | + $('#view').css('visibility', 'visible'); | ||
805 | + }); | ||
806 | + | ||
807 | + | ||
808 | + // returns the newly computed bounding box of the rectangle | ||
809 | + function adjustRectToFitText(n) { | ||
810 | + var text = n.select('text'), | ||
811 | + box = text.node().getBBox(), | ||
812 | + lab = config.labels; | ||
813 | + | ||
814 | + // not sure why n.data() returns an array of 1 element... | ||
815 | + var data = n.data()[0]; | ||
816 | + | ||
817 | + text.attr('text-anchor', 'middle') | ||
818 | + .attr('y', '-0.8em') | ||
819 | + .attr('x', lab.imgPad/2) | ||
820 | + ; | ||
821 | + | ||
822 | + // translate the bbox so that it is centered on [x,y] | ||
823 | + box.x = -box.width / 2; | ||
824 | + box.y = -box.height / 2; | ||
825 | + | ||
826 | + // add padding | ||
827 | + box.x -= (lab.padLR + lab.imgPad/2); | ||
828 | + box.width += lab.padLR * 2 + lab.imgPad; | ||
829 | + box.y -= lab.padTB; | ||
830 | + box.height += lab.padTB * 2; | ||
831 | + | ||
832 | + return box; | ||
833 | + } | ||
834 | + | ||
835 | + function boundsFromBox(box) { | ||
836 | + return { | ||
837 | + x1: box.x, | ||
838 | + y1: box.y, | ||
839 | + x2: box.x + box.width, | ||
840 | + y2: box.y + box.height | ||
841 | + }; | ||
842 | + } | ||
843 | + | ||
844 | + } | ||
845 | + | ||
846 | + function iconUrl(d) { | ||
847 | + return 'img/' + d.type + '.png'; | ||
848 | +// return config.iconUrl[d.icon]; | ||
849 | + } | ||
850 | + | ||
851 | + function translate(x, y) { | ||
852 | + return 'translate(' + x + ',' + y + ')'; | ||
853 | + } | ||
854 | + | ||
855 | + // prevents collisions amongst device nodes | ||
856 | + function preventCollisions() { | ||
857 | + var quadtree = d3.geom.quadtree(network.nodes), | ||
858 | + hrad = config.hostRadius; | ||
859 | + | ||
860 | + network.nodes.forEach(function(n) { | ||
861 | + var nx1, nx2, ny1, ny2; | ||
862 | + | ||
863 | + if (n.class === 'device') { | ||
864 | + nx1 = n.x + n.extent.left; | ||
865 | + nx2 = n.x + n.extent.right; | ||
866 | + ny1 = n.y + n.extent.top; | ||
867 | + ny2 = n.y + n.extent.bottom; | ||
868 | + | ||
869 | + } else { | ||
870 | + nx1 = n.x - hrad; | ||
871 | + nx2 = n.x + hrad; | ||
872 | + ny1 = n.y - hrad; | ||
873 | + ny2 = n.y + hrad; | ||
874 | + } | ||
875 | + | ||
876 | + quadtree.visit(function(quad, x1, y1, x2, y2) { | ||
877 | + if (quad.point && quad.point !== n) { | ||
878 | + // check if the rectangles/circles intersect | ||
879 | + var p = quad.point, | ||
880 | + px1, px2, py1, py2, ix; | ||
881 | + | ||
882 | + if (p.class === 'device') { | ||
883 | + px1 = p.x + p.extent.left; | ||
884 | + px2 = p.x + p.extent.right; | ||
885 | + py1 = p.y + p.extent.top; | ||
886 | + py2 = p.y + p.extent.bottom; | ||
887 | + | ||
888 | + } else { | ||
889 | + px1 = p.x - hrad; | ||
890 | + px2 = p.x + hrad; | ||
891 | + py1 = p.y - hrad; | ||
892 | + py2 = p.y + hrad; | ||
893 | + } | ||
894 | + | ||
895 | + ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2); | ||
896 | + | ||
897 | + if (ix) { | ||
898 | + var xa1 = nx2 - px1, // shift n left , p right | ||
899 | + xa2 = px2 - nx1, // shift n right, p left | ||
900 | + ya1 = ny2 - py1, // shift n up , p down | ||
901 | + ya2 = py2 - ny1, // shift n down , p up | ||
902 | + adj = Math.min(xa1, xa2, ya1, ya2); | ||
903 | + | ||
904 | + if (adj == xa1) { | ||
905 | + n.x -= adj / 2; | ||
906 | + p.x += adj / 2; | ||
907 | + } else if (adj == xa2) { | ||
908 | + n.x += adj / 2; | ||
909 | + p.x -= adj / 2; | ||
910 | + } else if (adj == ya1) { | ||
911 | + n.y -= adj / 2; | ||
912 | + p.y += adj / 2; | ||
913 | + } else if (adj == ya2) { | ||
914 | + n.y += adj / 2; | ||
915 | + p.y -= adj / 2; | ||
916 | + } | ||
917 | + } | ||
918 | + return ix; | ||
919 | + } | ||
920 | + }); | ||
921 | + | ||
922 | + }); | ||
923 | + } | ||
924 | + | ||
925 | + function tick(e) { | ||
926 | + network.numTicks++; | ||
927 | + | ||
928 | + if (config.options.layering) { | ||
929 | + // adjust the y-coord of each node, based on y-pos constraints | ||
930 | + network.nodes.forEach(function (n) { | ||
931 | + var z = e.alpha * n.constraint.weight; | ||
932 | + if (!isNaN(n.constraint.y)) { | ||
933 | + n.y = (n.constraint.y * z + n.y * (1 - z)); | ||
934 | + } | ||
935 | + }); | ||
936 | + } | ||
937 | + | ||
938 | + if (config.options.collisionPrevention && network.preventCollisions) { | ||
939 | + preventCollisions(); | ||
940 | + } | ||
941 | + | ||
942 | + var portHalfW = config.labels.port.width / 2, | ||
943 | + portHalfH = config.labels.port.height / 2; | ||
944 | + | ||
945 | + // clip visualization of links at bounds of nodes... | ||
946 | + network.link.each(function(d) { | ||
947 | + var xs = d.source.x, | ||
948 | + ys = d.source.y, | ||
949 | + xt = d.target.x, | ||
950 | + yt = d.target.y, | ||
951 | + line = new geo.LineSegment(xs, ys, xt, yt), | ||
952 | + e, ix, | ||
953 | + exs, eys, ext, eyt, | ||
954 | + pxs, pys, pxt, pyt; | ||
955 | + | ||
956 | + if (d.class === 'host') { | ||
957 | + // no adjustment for source end of link, since hosts are dots | ||
958 | + exs = xs; | ||
959 | + eys = ys; | ||
960 | + | ||
961 | + } else { | ||
962 | + for (e in d.source.edge) { | ||
963 | + ix = line.intersect(d.source.edge[e].offset(xs, ys)); | ||
964 | + if (ix.in1 && ix.in2) { | ||
965 | + exs = ix.x; | ||
966 | + eys = ix.y; | ||
967 | + | ||
968 | + // also pick off the port label intersection | ||
969 | + ix = line.intersect(d.source.portEdge[e].offset(xs, ys)); | ||
970 | + pxs = ix.x; | ||
971 | + pys = ix.y; | ||
972 | + break; | ||
973 | + } | ||
974 | + } | ||
975 | + } | ||
976 | + | ||
977 | + for (e in d.target.edge) { | ||
978 | + ix = line.intersect(d.target.edge[e].offset(xt, yt)); | ||
979 | + if (ix.in1 && ix.in2) { | ||
980 | + ext = ix.x; | ||
981 | + eyt = ix.y; | ||
982 | + | ||
983 | + // also pick off the port label intersection | ||
984 | + ix = line.intersect(d.target.portEdge[e].offset(xt, yt)); | ||
985 | + pxt = ix.x; | ||
986 | + pyt = ix.y; | ||
987 | + break; | ||
988 | + } | ||
989 | + } | ||
990 | + | ||
991 | + // adjust the endpoints of the link's line to match rectangles | ||
992 | + var sid = safeId(d.id); | ||
993 | + d3.select(this) | ||
994 | + .attr('x1', exs) | ||
995 | + .attr('y1', eys) | ||
996 | + .attr('x2', ext) | ||
997 | + .attr('y2', eyt); | ||
998 | + | ||
999 | + d3.select('#srcPort-' + sid) | ||
1000 | + .attr('x', pxs - portHalfW) | ||
1001 | + .attr('y', pys - portHalfH); | ||
1002 | + | ||
1003 | + d3.select('#tgtPort-' + sid) | ||
1004 | + .attr('x', pxt - portHalfW) | ||
1005 | + .attr('y', pyt - portHalfH); | ||
1006 | + | ||
1007 | + // TODO: fit label rect to size of port number. | ||
1008 | + d3.select('#srcText-' + sid) | ||
1009 | + .attr('x', pxs - 5) | ||
1010 | + .attr('y', pys + 3); | ||
1011 | + | ||
1012 | + d3.select('#tgtText-' + sid) | ||
1013 | + .attr('x', pxt - 5) | ||
1014 | + .attr('y', pyt + 3); | ||
1015 | + | ||
1016 | + }); | ||
1017 | + | ||
1018 | + // position each node by translating the node (group) by x,y | ||
1019 | + network.node | ||
1020 | + .attr('transform', function(d) { | ||
1021 | + return translate(d.x, d.y); | ||
1022 | + }); | ||
1023 | + | ||
1024 | + } | ||
1025 | + | ||
1026 | + // $('#docs-close').on('click', function() { | ||
1027 | + // deselectObject(); | ||
1028 | + // return false; | ||
1029 | + // }); | ||
1030 | + | ||
1031 | + // $(document).on('click', '.select-object', function() { | ||
1032 | + // var obj = graph.data[$(this).data('name')]; | ||
1033 | + // if (obj) { | ||
1034 | + // selectObject(obj); | ||
1035 | + // } | ||
1036 | + // return false; | ||
1037 | + // }); | ||
1038 | + | ||
1039 | + function findNodeFromData(d) { | ||
1040 | + var el = null; | ||
1041 | + network.node.filter('.' + d.class).each(function(n) { | ||
1042 | + if (n.id === d.id) { | ||
1043 | + el = d3.select(this); | ||
1044 | + } | ||
1045 | + }); | ||
1046 | + return el; | ||
1047 | + } | ||
1048 | + | ||
1049 | + function selectObject(obj, el) { | ||
1050 | + var node; | ||
1051 | + if (el) { | ||
1052 | + node = d3.select(el); | ||
1053 | + } else { | ||
1054 | + network.node.each(function(d) { | ||
1055 | + if (d == obj) { | ||
1056 | + node = d3.select(el = this); | ||
1057 | + } | ||
1058 | + }); | ||
1059 | + } | ||
1060 | + if (!node) return; | ||
1061 | + | ||
1062 | + if (node.classed('selected')) { | ||
1063 | + deselectObject(); | ||
1064 | + flyinPane(null); | ||
1065 | + return; | ||
1066 | + } | ||
1067 | + deselectObject(false); | ||
1068 | + | ||
1069 | + selected = { | ||
1070 | + obj : obj, | ||
1071 | + el : el | ||
1072 | + }; | ||
1073 | + | ||
1074 | + node.classed('selected', true); | ||
1075 | + flyinPane(obj); | ||
1076 | + } | ||
1077 | + | ||
1078 | + function deselectObject(doResize) { | ||
1079 | + // Review: logic of 'resize(...)' function. | ||
1080 | + if (doResize || typeof doResize == 'undefined') { | ||
1081 | + resize(false); | ||
1082 | + } | ||
1083 | + | ||
1084 | + // deselect all nodes in the network... | ||
1085 | + network.node.classed('selected', false); | ||
1086 | + selected = {}; | ||
1087 | + flyinPane(null); | ||
1088 | + } | ||
1089 | + | ||
1090 | + function flyinPane(obj) { | ||
1091 | + var pane = d3.select('#flyout'), | ||
1092 | + url; | ||
1093 | + | ||
1094 | + if (obj) { | ||
1095 | + // go get details of the selected object from the server... | ||
1096 | + url = detailJsonUrl(obj.id); | ||
1097 | + d3.json(url, function (err, data) { | ||
1098 | + if (err) { | ||
1099 | + alert('Oops! Error reading JSON...\n\n' + | ||
1100 | + 'URL: ' + url + '\n\n' + | ||
1101 | + 'Error: ' + err.message); | ||
1102 | + return; | ||
1103 | + } | ||
1104 | +// console.log("JSON data... " + url); | ||
1105 | +// console.log(data); | ||
1106 | + | ||
1107 | + displayDetails(data, pane); | ||
1108 | + }); | ||
1109 | + | ||
1110 | + } else { | ||
1111 | + // hide pane | ||
1112 | + pane.transition().duration(750) | ||
1113 | + .style('right', '-320px') | ||
1114 | + .style('opacity', 0.0); | ||
1115 | + } | ||
1116 | + } | ||
1117 | + | ||
1118 | + function displayDetails(data, pane) { | ||
1119 | + $('#flyout').empty(); | ||
1120 | + | ||
1121 | + var title = pane.append("h2"), | ||
1122 | + table = pane.append("table"), | ||
1123 | + tbody = table.append("tbody"); | ||
1124 | + | ||
1125 | + $('<img src="img/' + data.type + '.png">').appendTo(title); | ||
1126 | + $('<span>').attr('class', 'icon').text(data.id).appendTo(title); | ||
1127 | + | ||
1128 | + | ||
1129 | + // TODO: consider using d3 data bind to TR/TD | ||
1130 | + | ||
1131 | + data.propOrder.forEach(function(p) { | ||
1132 | + if (p === '-') { | ||
1133 | + addSep(tbody); | ||
1134 | + } else { | ||
1135 | + addProp(tbody, p, data.props[p]); | ||
1136 | + } | ||
1137 | + }); | ||
1138 | + | ||
1139 | + function addSep(tbody) { | ||
1140 | + var tr = tbody.append('tr'); | ||
1141 | + $('<hr>').appendTo(tr.append('td').attr('colspan', 2)); | ||
1142 | + } | ||
1143 | + | ||
1144 | + function addProp(tbody, label, value) { | ||
1145 | + var tr = tbody.append('tr'); | ||
1146 | + | ||
1147 | + tr.append('td') | ||
1148 | + .attr('class', 'label') | ||
1149 | + .text(label + ' :'); | ||
1150 | + | ||
1151 | + tr.append('td') | ||
1152 | + .attr('class', 'value') | ||
1153 | + .text(value); | ||
1154 | + } | ||
1155 | + | ||
1156 | + // show pane | ||
1157 | + pane.transition().duration(750) | ||
1158 | + .style('right', '20px') | ||
1159 | + .style('opacity', 1.0); | ||
1160 | + } | ||
1161 | + | ||
1162 | + function highlightObject(obj) { | ||
1163 | + if (obj) { | ||
1164 | + if (obj != highlighted) { | ||
1165 | + // TODO set or clear "inactive" class on nodes, based on criteria | ||
1166 | + network.node.classed('inactive', function(d) { | ||
1167 | + // return (obj !== d && | ||
1168 | + // d.relation(obj.id)); | ||
1169 | + return (obj !== d); | ||
1170 | + }); | ||
1171 | + // TODO: same with links | ||
1172 | + network.link.classed('inactive', function(d) { | ||
1173 | + return (obj !== d.source && obj !== d.target); | ||
1174 | + }); | ||
1175 | + } | ||
1176 | + highlighted = obj; | ||
1177 | + } else { | ||
1178 | + if (highlighted) { | ||
1179 | + // clear the inactive flag (no longer suppressed visually) | ||
1180 | + network.node.classed('inactive', false); | ||
1181 | + network.link.classed('inactive', false); | ||
1182 | + } | ||
1183 | + highlighted = null; | ||
1184 | + | ||
1185 | + } | ||
1186 | + } | ||
1187 | + | ||
1188 | + function hoverObject(obj) { | ||
1189 | + if (obj) { | ||
1190 | + hovered = obj; | ||
1191 | + } else { | ||
1192 | + if (hovered) { | ||
1193 | + hovered = null; | ||
1194 | + } | ||
1195 | + } | ||
1196 | + } | ||
1197 | + | ||
1198 | + | ||
1199 | + function resize() { | ||
1200 | + netView.height = window.innerHeight - config.mastHeight; | ||
1201 | + netView.width = window.innerWidth; | ||
1202 | + $('#view') | ||
1203 | + .css('height', netView.height + 'px') | ||
1204 | + .css('width', netView.width + 'px'); | ||
1205 | + | ||
1206 | + network.forceWidth = netView.width - config.force.marginLR; | ||
1207 | + network.forceHeight = netView.height - config.force.marginTB; | ||
1208 | + } | ||
1209 | + | ||
1210 | + // ====================================================================== | ||
1211 | + // register with the UI framework | ||
1212 | + | ||
1213 | + onos.ui.addView('topo', { | ||
1214 | + load: loadNetworkView | ||
1215 | + }); | ||
1216 | + | ||
1217 | + | ||
1218 | +}(ONOS)); | ||
1219 | + |
... | @@ -15,7 +15,7 @@ | ... | @@ -15,7 +15,7 @@ |
15 | */ | 15 | */ |
16 | 16 | ||
17 | /* | 17 | /* |
18 | - ONOS network topology viewer - PoC version 1.0 | 18 | + ONOS network topology viewer - version 1.1 |
19 | 19 | ||
20 | @author Simon Hunt | 20 | @author Simon Hunt |
21 | */ | 21 | */ |
... | @@ -25,7 +25,7 @@ | ... | @@ -25,7 +25,7 @@ |
25 | 25 | ||
26 | // configuration data | 26 | // configuration data |
27 | var config = { | 27 | var config = { |
28 | - useLiveData: true, | 28 | + useLiveData: false, |
29 | debugOn: false, | 29 | debugOn: false, |
30 | debug: { | 30 | debug: { |
31 | showNodeXY: false, | 31 | showNodeXY: false, |
... | @@ -34,7 +34,7 @@ | ... | @@ -34,7 +34,7 @@ |
34 | options: { | 34 | options: { |
35 | layering: true, | 35 | layering: true, |
36 | collisionPrevention: true, | 36 | collisionPrevention: true, |
37 | - loadBackground: true | 37 | + showBackground: true |
38 | }, | 38 | }, |
39 | backgroundUrl: 'img/us-map.png', | 39 | backgroundUrl: 'img/us-map.png', |
40 | data: { | 40 | data: { |
... | @@ -55,22 +55,7 @@ | ... | @@ -55,22 +55,7 @@ |
55 | pkt: 'img/pkt.png', | 55 | pkt: 'img/pkt.png', |
56 | opt: 'img/opt.png' | 56 | opt: 'img/opt.png' |
57 | }, | 57 | }, |
58 | - mastHeight: 36, | ||
59 | force: { | 58 | force: { |
60 | - note: 'node.class or link.class is used to differentiate', | ||
61 | - linkDistance: { | ||
62 | - infra: 200, | ||
63 | - host: 40 | ||
64 | - }, | ||
65 | - linkStrength: { | ||
66 | - infra: 1.0, | ||
67 | - host: 1.0 | ||
68 | - }, | ||
69 | - charge: { | ||
70 | - device: -800, | ||
71 | - host: -1000 | ||
72 | - }, | ||
73 | - ticksWithoutCollisions: 50, | ||
74 | marginLR: 20, | 59 | marginLR: 20, |
75 | marginTB: 20, | 60 | marginTB: 20, |
76 | translate: function() { | 61 | translate: function() { |
... | @@ -78,39 +63,19 @@ | ... | @@ -78,39 +63,19 @@ |
78 | config.force.marginLR + ',' + | 63 | config.force.marginLR + ',' + |
79 | config.force.marginTB + ')'; | 64 | config.force.marginTB + ')'; |
80 | } | 65 | } |
81 | - }, | ||
82 | - labels: { | ||
83 | - imgPad: 16, | ||
84 | - padLR: 8, | ||
85 | - padTB: 6, | ||
86 | - marginLR: 3, | ||
87 | - marginTB: 2, | ||
88 | - port: { | ||
89 | - gap: 3, | ||
90 | - width: 18, | ||
91 | - height: 14 | ||
92 | - } | ||
93 | - }, | ||
94 | - icons: { | ||
95 | - w: 32, | ||
96 | - h: 32, | ||
97 | - xoff: -12, | ||
98 | - yoff: -8 | ||
99 | - }, | ||
100 | - constraints: { | ||
101 | - ypos: { | ||
102 | - host: 0.05, | ||
103 | - switch: 0.3, | ||
104 | - roadm: 0.7 | ||
105 | } | 66 | } |
106 | - }, | ||
107 | - hostLinkWidth: 1.0, | ||
108 | - hostRadius: 7, | ||
109 | - mouseOutTimerDelayMs: 120 | ||
110 | }; | 67 | }; |
111 | 68 | ||
69 | + // radio buttons | ||
70 | + var btnSet = [ | ||
71 | + { id: 'showAll', text: 'All Layers' }, | ||
72 | + { id: 'showPkt', text: 'Packet Only' }, | ||
73 | + { id: 'showOpt', text: 'Optical Only' } | ||
74 | + ]; | ||
75 | + | ||
112 | // state variables | 76 | // state variables |
113 | - var view = {}, | 77 | + var svg, |
78 | + bgImg, | ||
114 | network = {}, | 79 | network = {}, |
115 | selected = {}, | 80 | selected = {}, |
116 | highlighted = null, | 81 | highlighted = null, |
... | @@ -119,84 +84,19 @@ | ... | @@ -119,84 +84,19 @@ |
119 | portLabelsOn = false; | 84 | portLabelsOn = false; |
120 | 85 | ||
121 | 86 | ||
122 | - function debug(what) { | 87 | + // ============================== |
123 | - return config.debugOn && config.debug[what]; | 88 | + // Private functions |
124 | - } | ||
125 | - | ||
126 | - function urlData() { | ||
127 | - return config.data[config.useLiveData ? 'live' : 'fake']; | ||
128 | - } | ||
129 | - | ||
130 | - function networkJsonUrl() { | ||
131 | - return urlData().jsonUrl; | ||
132 | - } | ||
133 | - | ||
134 | - function safeId(id) { | ||
135 | - return id.replace(/[^a-z0-9]/gi, '_'); | ||
136 | - } | ||
137 | - | ||
138 | - function detailJsonUrl(id) { | ||
139 | - var u = urlData(), | ||
140 | - encId = config.useLiveData ? encodeURIComponent(id) : safeId(id); | ||
141 | - return u.detailPrefix + encId + u.detailSuffix; | ||
142 | - } | ||
143 | - | ||
144 | - | ||
145 | - // load the topology view of the network | ||
146 | - function loadNetworkView() { | ||
147 | - // Hey, here I am, calling something on the ONOS api: | ||
148 | - api.printTime(); | ||
149 | - | ||
150 | - resize(); | ||
151 | - | ||
152 | - // go get our network data from the server... | ||
153 | - var url = networkJsonUrl(); | ||
154 | - d3.json(url , function (err, data) { | ||
155 | - if (err) { | ||
156 | - alert('Oops! Error reading JSON...\n\n' + | ||
157 | - 'URL: ' + url + '\n\n' + | ||
158 | - 'Error: ' + err.message); | ||
159 | - return; | ||
160 | - } | ||
161 | -// console.log("here is the JSON data..."); | ||
162 | -// console.log(data); | ||
163 | - | ||
164 | - network.data = data; | ||
165 | - drawNetwork(); | ||
166 | - }); | ||
167 | - | ||
168 | - // while we wait for the data, set up the handlers... | ||
169 | - setUpClickHandler(); | ||
170 | - setUpRadioButtonHandler(); | ||
171 | - setUpKeyHandler(); | ||
172 | - $(window).on('resize', resize); | ||
173 | - } | ||
174 | 89 | ||
175 | - function setUpClickHandler() { | 90 | + // set the size of the SVG layer (or other element) to that of the view |
176 | - // click handler for "selectable" objects | 91 | + function setSize(view, el) { |
177 | - $(document).on('click', '.select-object', function () { | 92 | + var thing = el || svg; |
178 | - // when any object of class "select-object" is clicked... | 93 | + thing.attr({ |
179 | - var obj = network.lookup[$(this).data('id')]; | 94 | + width: view.width(), |
180 | - if (obj) { | 95 | + height: view.height() |
181 | - selectObject(obj); | ||
182 | - } | ||
183 | - // stop propagation of event (I think) ... | ||
184 | - return false; | ||
185 | }); | 96 | }); |
186 | } | 97 | } |
187 | 98 | ||
188 | - function setUpRadioButtonHandler() { | 99 | + function doRadio(view, id) { |
189 | - d3.selectAll('#displayModes .radio').on('click', function () { | ||
190 | - var id = d3.select(this).attr('id'); | ||
191 | - if (id !== viewMode) { | ||
192 | - radioButton('displayModes', id); | ||
193 | - viewMode = id; | ||
194 | - doRadioAction(id); | ||
195 | - } | ||
196 | - }); | ||
197 | - } | ||
198 | - | ||
199 | - function doRadioAction(id) { | ||
200 | showAllLayers(); | 100 | showAllLayers(); |
201 | if (id === 'showPkt') { | 101 | if (id === 'showPkt') { |
202 | showPacketLayer(); | 102 | showPacketLayer(); |
... | @@ -206,1014 +106,68 @@ | ... | @@ -206,1014 +106,68 @@ |
206 | } | 106 | } |
207 | 107 | ||
208 | function showAllLayers() { | 108 | function showAllLayers() { |
209 | - network.node.classed('inactive', false); | 109 | +// network.node.classed('inactive', false); |
210 | - network.link.classed('inactive', false); | 110 | +// network.link.classed('inactive', false); |
211 | - d3.selectAll('svg .port').classed('inactive', false) | 111 | +// d3.selectAll('svg .port').classed('inactive', false); |
212 | - d3.selectAll('svg .portText').classed('inactive', false) | 112 | +// d3.selectAll('svg .portText').classed('inactive', false); |
113 | + alert('show all layers'); | ||
213 | } | 114 | } |
214 | 115 | ||
215 | function showPacketLayer() { | 116 | function showPacketLayer() { |
216 | - network.node.each(function(d) { | 117 | + alert('show packet layer'); |
217 | - // deactivate nodes that are not hosts or switches | ||
218 | - if (d.class === 'device' && d.type !== 'switch') { | ||
219 | - d3.select(this).classed('inactive', true); | ||
220 | - } | ||
221 | - }); | ||
222 | - | ||
223 | - network.link.each(function(lnk) { | ||
224 | - // deactivate infrastructure links that have opt's as endpoints | ||
225 | - if (lnk.source.type === 'roadm' || lnk.target.type === 'roadm') { | ||
226 | - d3.select(this).classed('inactive', true); | ||
227 | - } | ||
228 | - }); | ||
229 | - | ||
230 | - // deactivate non-packet ports | ||
231 | - d3.selectAll('svg .optPort').classed('inactive', true) | ||
232 | } | 118 | } |
233 | 119 | ||
234 | function showOpticalLayer() { | 120 | function showOpticalLayer() { |
235 | - network.node.each(function(d) { | 121 | + alert('show optical layer'); |
236 | - // deactivate nodes that are not optical devices | ||
237 | - if (d.type !== 'roadm') { | ||
238 | - d3.select(this).classed('inactive', true); | ||
239 | - } | ||
240 | - }); | ||
241 | - | ||
242 | - network.link.each(function(lnk) { | ||
243 | - // deactivate infrastructure links that have opt's as endpoints | ||
244 | - if (lnk.source.type !== 'roadm' || lnk.target.type !== 'roadm') { | ||
245 | - d3.select(this).classed('inactive', true); | ||
246 | - } | ||
247 | - }); | ||
248 | - | ||
249 | - // deactivate non-packet ports | ||
250 | - d3.selectAll('svg .pktPort').classed('inactive', true) | ||
251 | - } | ||
252 | - | ||
253 | - function setUpKeyHandler() { | ||
254 | - d3.select('body') | ||
255 | - .on('keydown', function () { | ||
256 | - processKeyEvent(); | ||
257 | - if (debug('showKeyHandler')) { | ||
258 | - network.svg.append('text') | ||
259 | - .attr('x', 5) | ||
260 | - .attr('y', 15) | ||
261 | - .style('font-size', '20pt') | ||
262 | - .text('keyCode: ' + d3.event.keyCode + | ||
263 | - ' applied to : ' + contextLabel()) | ||
264 | - .transition().duration(2000) | ||
265 | - .style('font-size', '2pt') | ||
266 | - .style('fill-opacity', 0.01) | ||
267 | - .remove(); | ||
268 | - } | ||
269 | - }); | ||
270 | - } | ||
271 | - | ||
272 | - function contextLabel() { | ||
273 | - return hovered === null ? "(nothing)" : hovered.id; | ||
274 | - } | ||
275 | - | ||
276 | - function radioButton(group, id) { | ||
277 | - d3.selectAll("#" + group + " .radio").classed("active", false); | ||
278 | - d3.select("#" + group + " #" + id).classed("active", true); | ||
279 | - } | ||
280 | - | ||
281 | - function processKeyEvent() { | ||
282 | - var code = d3.event.keyCode; | ||
283 | - switch (code) { | ||
284 | - case 66: // B | ||
285 | - toggleBackground(); | ||
286 | - break; | ||
287 | - case 71: // G | ||
288 | - cycleLayout(); | ||
289 | - break; | ||
290 | - case 76: // L | ||
291 | - cycleLabels(); | ||
292 | - break; | ||
293 | - case 80: // P | ||
294 | - togglePorts(); | ||
295 | - break; | ||
296 | - case 85: // U | ||
297 | - unpin(); | ||
298 | - break; | ||
299 | - } | ||
300 | - | ||
301 | - } | ||
302 | - | ||
303 | - function toggleBackground() { | ||
304 | - var bg = d3.select('#bg'), | ||
305 | - vis = bg.style('visibility'), | ||
306 | - newvis = (vis === 'hidden') ? 'visible' : 'hidden'; | ||
307 | - bg.style('visibility', newvis); | ||
308 | - } | ||
309 | - | ||
310 | - function cycleLayout() { | ||
311 | - config.options.layering = !config.options.layering; | ||
312 | - network.force.resume(); | ||
313 | - } | ||
314 | - | ||
315 | - function cycleLabels() { | ||
316 | - console.log('Cycle Labels - context = ' + contextLabel()); | ||
317 | - } | ||
318 | - | ||
319 | - function togglePorts() { | ||
320 | - portLabelsOn = !portLabelsOn; | ||
321 | - var portVis = portLabelsOn ? 'visible' : 'hidden'; | ||
322 | - d3.selectAll('.port').style('visibility', portVis); | ||
323 | - d3.selectAll('.portText').style('visibility', portVis); | ||
324 | - } | ||
325 | - | ||
326 | - function unpin() { | ||
327 | - if (hovered) { | ||
328 | - hovered.fixed = false; | ||
329 | - findNodeFromData(hovered).classed('fixed', false); | ||
330 | - network.force.resume(); | ||
331 | - } | ||
332 | - console.log('Unpin - context = ' + contextLabel()); | ||
333 | - } | ||
334 | - | ||
335 | - | ||
336 | - // ======================================================== | ||
337 | - | ||
338 | - function drawNetwork() { | ||
339 | - $('#view').empty(); | ||
340 | - | ||
341 | - prepareNodesAndLinks(); | ||
342 | - createLayout(); | ||
343 | - console.log("\n\nHere is the augmented network object..."); | ||
344 | - console.log(network); | ||
345 | - } | ||
346 | - | ||
347 | - function prepareNodesAndLinks() { | ||
348 | - network.lookup = {}; | ||
349 | - network.nodes = []; | ||
350 | - network.links = []; | ||
351 | - | ||
352 | - var nw = network.forceWidth, | ||
353 | - nh = network.forceHeight; | ||
354 | - | ||
355 | - function yPosConstraintForNode(n) { | ||
356 | - return config.constraints.ypos[n.type || 'host']; | ||
357 | } | 122 | } |
358 | 123 | ||
359 | - // Note that both 'devices' and 'hosts' get mapped into the nodes array | 124 | + // ============================== |
125 | + // View life-cycle callbacks | ||
360 | 126 | ||
361 | - // first, the devices... | 127 | + function preload(view, ctx) { |
362 | - network.data.devices.forEach(function(n) { | 128 | + var w = view.width(), |
363 | - var ypc = yPosConstraintForNode(n), | 129 | + h = view.height(), |
364 | - ix = Math.random() * 0.6 * nw + 0.2 * nw, | 130 | + idBg = view.uid('bg'), |
365 | - iy = ypc * nh, | 131 | + showBg = config.options.showBackground ? 'visible' : 'hidden'; |
366 | - node = { | ||
367 | - id: n.id, | ||
368 | - labels: n.labels, | ||
369 | - class: 'device', | ||
370 | - icon: 'device', | ||
371 | - type: n.type, | ||
372 | - x: ix, | ||
373 | - y: iy, | ||
374 | - constraint: { | ||
375 | - weight: 0.7, | ||
376 | - y: iy | ||
377 | - } | ||
378 | - }; | ||
379 | - network.lookup[n.id] = node; | ||
380 | - network.nodes.push(node); | ||
381 | - }); | ||
382 | - | ||
383 | - // then, the hosts... | ||
384 | - network.data.hosts.forEach(function(n) { | ||
385 | - var ypc = yPosConstraintForNode(n), | ||
386 | - ix = Math.random() * 0.6 * nw + 0.2 * nw, | ||
387 | - iy = ypc * nh, | ||
388 | - node = { | ||
389 | - id: n.id, | ||
390 | - labels: n.labels, | ||
391 | - class: 'host', | ||
392 | - icon: 'host', | ||
393 | - type: n.type, | ||
394 | - x: ix, | ||
395 | - y: iy, | ||
396 | - constraint: { | ||
397 | - weight: 0.7, | ||
398 | - y: iy | ||
399 | - } | ||
400 | - }; | ||
401 | - network.lookup[n.id] = node; | ||
402 | - network.nodes.push(node); | ||
403 | - }); | ||
404 | - | ||
405 | - | ||
406 | - // now, process the explicit links... | ||
407 | - network.data.links.forEach(function(lnk) { | ||
408 | - var src = network.lookup[lnk.src], | ||
409 | - dst = network.lookup[lnk.dst], | ||
410 | - id = src.id + "-" + dst.id; | ||
411 | - | ||
412 | - var link = { | ||
413 | - class: 'infra', | ||
414 | - id: id, | ||
415 | - type: lnk.type, | ||
416 | - width: lnk.linkWidth, | ||
417 | - source: src, | ||
418 | - srcPort: lnk.srcPort, | ||
419 | - target: dst, | ||
420 | - tgtPort: lnk.dstPort, | ||
421 | - strength: config.force.linkStrength.infra | ||
422 | - }; | ||
423 | - network.links.push(link); | ||
424 | - }); | ||
425 | - | ||
426 | - // finally, infer host links... | ||
427 | - network.data.hosts.forEach(function(n) { | ||
428 | - var src = network.lookup[n.id], | ||
429 | - dst = network.lookup[n.cp.device], | ||
430 | - id = src.id + "-" + dst.id; | ||
431 | - | ||
432 | - var link = { | ||
433 | - class: 'host', | ||
434 | - id: id, | ||
435 | - type: 'hostLink', | ||
436 | - width: config.hostLinkWidth, | ||
437 | - source: src, | ||
438 | - target: dst, | ||
439 | - strength: config.force.linkStrength.host | ||
440 | - }; | ||
441 | - network.links.push(link); | ||
442 | - }); | ||
443 | - } | ||
444 | - | ||
445 | - function createLayout() { | ||
446 | - | ||
447 | - var cfg = config.force; | ||
448 | 132 | ||
449 | - network.force = d3.layout.force() | 133 | + // NOTE: view.$div is a D3 selection of the view's div |
450 | - .size([network.forceWidth, network.forceHeight]) | 134 | + svg = view.$div.append('svg'); |
451 | - .nodes(network.nodes) | 135 | + setSize(view); |
452 | - .links(network.links) | 136 | + svg.append('g') |
453 | - .linkStrength(function(d) { return cfg.linkStrength[d.class]; }) | ||
454 | - .linkDistance(function(d) { return cfg.linkDistance[d.class]; }) | ||
455 | - .charge(function(d) { return cfg.charge[d.class]; }) | ||
456 | - .on('tick', tick); | ||
457 | - | ||
458 | - network.svg = d3.select('#view').append('svg') | ||
459 | - .attr('width', view.width) | ||
460 | - .attr('height', view.height) | ||
461 | - .append('g') | ||
462 | .attr('transform', config.force.translate()); | 137 | .attr('transform', config.force.translate()); |
463 | -// .attr('id', 'zoomable') | ||
464 | -// .call(d3.behavior.zoom().on("zoom", zoomRedraw)); | ||
465 | 138 | ||
466 | - network.svg.append('svg:image') | 139 | + // load the background image |
140 | + bgImg = svg.append('svg:image') | ||
467 | .attr({ | 141 | .attr({ |
468 | - id: 'bg', | 142 | + id: idBg, |
469 | - width: view.width, | 143 | + width: w, |
470 | - height: view.height, | 144 | + height: h, |
471 | 'xlink:href': config.backgroundUrl | 145 | 'xlink:href': config.backgroundUrl |
472 | }) | 146 | }) |
473 | - .style('visibility', | 147 | + .style({ |
474 | - config.options.loadBackground ? 'visible' : 'hidden'); | 148 | + visibility: showBg |
475 | - | ||
476 | -// function zoomRedraw() { | ||
477 | -// d3.select("#zoomable").attr("transform", | ||
478 | -// "translate(" + d3.event.translate + ")" | ||
479 | -// + " scale(" + d3.event.scale + ")"); | ||
480 | -// } | ||
481 | - | ||
482 | - // TODO: move glow/blur stuff to util script | ||
483 | - var glow = network.svg.append('filter') | ||
484 | - .attr('x', '-50%') | ||
485 | - .attr('y', '-50%') | ||
486 | - .attr('width', '200%') | ||
487 | - .attr('height', '200%') | ||
488 | - .attr('id', 'blue-glow'); | ||
489 | - | ||
490 | - glow.append('feColorMatrix') | ||
491 | - .attr('type', 'matrix') | ||
492 | - .attr('values', '0 0 0 0 0 ' + | ||
493 | - '0 0 0 0 0 ' + | ||
494 | - '0 0 0 0 .7 ' + | ||
495 | - '0 0 0 1 0 '); | ||
496 | - | ||
497 | - glow.append('feGaussianBlur') | ||
498 | - .attr('stdDeviation', 3) | ||
499 | - .attr('result', 'coloredBlur'); | ||
500 | - | ||
501 | - glow.append('feMerge').selectAll('feMergeNode') | ||
502 | - .data(['coloredBlur', 'SourceGraphic']) | ||
503 | - .enter().append('feMergeNode') | ||
504 | - .attr('in', String); | ||
505 | - | ||
506 | - // TODO: legend (and auto adjust on scroll) | ||
507 | -// $('#view').on('scroll', function() { | ||
508 | -// | ||
509 | -// }); | ||
510 | - | ||
511 | - | ||
512 | - // TODO: move drag behavior into separate method. | ||
513 | - // == define node drag behavior... | ||
514 | - network.draggedThreshold = d3.scale.linear() | ||
515 | - .domain([0, 0.1]) | ||
516 | - .range([5, 20]) | ||
517 | - .clamp(true); | ||
518 | - | ||
519 | - function dragged(d) { | ||
520 | - var threshold = network.draggedThreshold(network.force.alpha()), | ||
521 | - dx = d.oldX - d.px, | ||
522 | - dy = d.oldY - d.py; | ||
523 | - if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) { | ||
524 | - d.dragged = true; | ||
525 | - } | ||
526 | - return d.dragged; | ||
527 | - } | ||
528 | - | ||
529 | - network.drag = d3.behavior.drag() | ||
530 | - .origin(function(d) { return d; }) | ||
531 | - .on('dragstart', function(d) { | ||
532 | - d.oldX = d.x; | ||
533 | - d.oldY = d.y; | ||
534 | - d.dragged = false; | ||
535 | - d.fixed |= 2; | ||
536 | - }) | ||
537 | - .on('drag', function(d) { | ||
538 | - d.px = d3.event.x; | ||
539 | - d.py = d3.event.y; | ||
540 | - if (dragged(d)) { | ||
541 | - if (!network.force.alpha()) { | ||
542 | - network.force.alpha(.025); | ||
543 | - } | ||
544 | - } | ||
545 | - }) | ||
546 | - .on('dragend', function(d) { | ||
547 | - if (!dragged(d)) { | ||
548 | - selectObject(d, this); | ||
549 | - } | ||
550 | - d.fixed &= ~6; | ||
551 | - | ||
552 | - // once we've finished moving, pin the node in position, | ||
553 | - // if it is a device (not a host) | ||
554 | - if (d.class === 'device') { | ||
555 | - d.fixed = true; | ||
556 | - d3.select(this).classed('fixed', true) | ||
557 | - } | ||
558 | - }); | ||
559 | - | ||
560 | - $('#view').on('click', function(e) { | ||
561 | - if (!$(e.target).closest('.node').length) { | ||
562 | - deselectObject(); | ||
563 | - } | ||
564 | - }); | ||
565 | - | ||
566 | - // ............................................................... | ||
567 | - | ||
568 | - // add links to the display | ||
569 | - network.link = network.svg.append('g').attr('id', 'links') | ||
570 | - .selectAll('.link') | ||
571 | - .data(network.force.links(), function(d) {return d.id}) | ||
572 | - .enter().append('line') | ||
573 | - .attr('class', function(d) {return 'link ' + d.class}); | ||
574 | - | ||
575 | - network.linkSrcPort = network.svg.append('g') | ||
576 | - .attr({ | ||
577 | - id: 'srcPorts', | ||
578 | - class: 'portLayer' | ||
579 | - }); | ||
580 | - network.linkTgtPort = network.svg.append('g') | ||
581 | - .attr({ | ||
582 | - id: 'tgtPorts', | ||
583 | - class: 'portLayer' | ||
584 | - }); | ||
585 | - | ||
586 | - var portVis = portLabelsOn ? 'visible' : 'hidden', | ||
587 | - pw = config.labels.port.width, | ||
588 | - ph = config.labels.port.height; | ||
589 | - | ||
590 | - network.link.filter('.infra').each(function(d) { | ||
591 | - var srcType = d.source.type === 'roadm' ? 'optPort' : 'pktPort', | ||
592 | - tgtType = d.target.type === 'roadm' ? 'optPort' : 'pktPort'; | ||
593 | - | ||
594 | - if (d.source.type) | ||
595 | - | ||
596 | - network.linkSrcPort.append('rect').attr({ | ||
597 | - id: 'srcPort-' + safeId(d.id), | ||
598 | - class: 'port ' + srcType, | ||
599 | - width: pw, | ||
600 | - height: ph, | ||
601 | - rx: 4, | ||
602 | - ry: 4 | ||
603 | - }).style('visibility', portVis); | ||
604 | - | ||
605 | - network.linkTgtPort.append('rect').attr({ | ||
606 | - id: 'tgtPort-' + safeId(d.id), | ||
607 | - class: 'port ' + tgtType, | ||
608 | - width: pw, | ||
609 | - height: ph, | ||
610 | - rx: 4, | ||
611 | - ry: 4 | ||
612 | - }).style('visibility', portVis); | ||
613 | - | ||
614 | - network.linkSrcPort.append('text').attr({ | ||
615 | - id: 'srcText-' + safeId(d.id), | ||
616 | - class: 'portText ' + srcType | ||
617 | - }).text(d.srcPort) | ||
618 | - .style('visibility', portVis); | ||
619 | - | ||
620 | - network.linkTgtPort.append('text').attr({ | ||
621 | - id: 'tgtText-' + safeId(d.id), | ||
622 | - class: 'portText ' + tgtType | ||
623 | - }).text(d.tgtPort) | ||
624 | - .style('visibility', portVis); | ||
625 | }); | 149 | }); |
626 | - | ||
627 | - // ............................................................... | ||
628 | - | ||
629 | - // add nodes to the display | ||
630 | - network.node = network.svg.selectAll('.node') | ||
631 | - .data(network.force.nodes(), function(d) {return d.id}) | ||
632 | - .enter().append('g') | ||
633 | - .attr('class', function(d) { | ||
634 | - var cls = 'node ' + d.class; | ||
635 | - if (d.type) { | ||
636 | - cls += ' ' + d.type; | ||
637 | - } | ||
638 | - return cls; | ||
639 | - }) | ||
640 | - .attr('transform', function(d) { | ||
641 | - return translate(d.x, d.y); | ||
642 | - }) | ||
643 | - .call(network.drag) | ||
644 | - .on('mouseover', function(d) { | ||
645 | - // TODO: show tooltip | ||
646 | - if (network.mouseoutTimeout) { | ||
647 | - clearTimeout(network.mouseoutTimeout); | ||
648 | - network.mouseoutTimeout = null; | ||
649 | } | 150 | } |
650 | - hoverObject(d); | ||
651 | - }) | ||
652 | - .on('mouseout', function(d) { | ||
653 | - // TODO: hide tooltip | ||
654 | - if (network.mouseoutTimeout) { | ||
655 | - clearTimeout(network.mouseoutTimeout); | ||
656 | - network.mouseoutTimeout = null; | ||
657 | - } | ||
658 | - network.mouseoutTimeout = setTimeout(function() { | ||
659 | - hoverObject(null); | ||
660 | - }, config.mouseOutTimerDelayMs); | ||
661 | - }); | ||
662 | 151 | ||
663 | 152 | ||
664 | - // deal with device nodes first | 153 | + function load(view, ctx) { |
665 | - network.nodeRect = network.node.filter('.device') | 154 | + view.setRadio(btnSet, doRadio); |
666 | - .append('rect') | ||
667 | - .attr({ | ||
668 | - rx: 5, | ||
669 | - ry: 5, | ||
670 | - width: 100, | ||
671 | - height: 12 | ||
672 | - }); | ||
673 | - // note that width/height are adjusted to fit the label text | ||
674 | - // then padded, and space made for the icon. | ||
675 | 155 | ||
676 | - network.node.filter('.device').each(function(d) { | ||
677 | - var node = d3.select(this), | ||
678 | - icon = iconUrl(d); | ||
679 | - | ||
680 | - node.append('text') | ||
681 | - // TODO: add label cycle behavior | ||
682 | - .text(d.id) | ||
683 | - .attr('dy', '1.1em'); | ||
684 | - | ||
685 | - if (icon) { | ||
686 | - var cfg = config.icons; | ||
687 | - node.append('svg:image') | ||
688 | - .attr({ | ||
689 | - width: cfg.w, | ||
690 | - height: cfg.h, | ||
691 | - 'xlink:href': icon | ||
692 | - }); | ||
693 | - // note, icon relative positioning (x,y) is done after we have | ||
694 | - // adjusted the bounds of the rectangle... | ||
695 | } | 156 | } |
696 | 157 | ||
697 | - // debug function to show the modelled x,y coordinates of nodes... | 158 | + function resize(view, ctx) { |
698 | - if (debug('showNodeXY')) { | 159 | + setSize(view); |
699 | - node.select('rect').attr('fill-opacity', 0.5); | 160 | + setSize(view, bgImg); |
700 | - node.append('circle') | ||
701 | - .attr({ | ||
702 | - class: 'debug', | ||
703 | - cx: 0, | ||
704 | - cy: 0, | ||
705 | - r: '3px' | ||
706 | - }); | ||
707 | } | 161 | } |
708 | - }); | ||
709 | 162 | ||
710 | - // now process host nodes | ||
711 | - network.nodeCircle = network.node.filter('.host') | ||
712 | - .append('circle') | ||
713 | - .attr({ | ||
714 | - r: config.hostRadius | ||
715 | - }); | ||
716 | 163 | ||
717 | - network.node.filter('.host').each(function(d) { | 164 | + // ============================== |
718 | - var node = d3.select(this), | 165 | + // View registration |
719 | - icon = iconUrl(d); | ||
720 | - | ||
721 | - // debug function to show the modelled x,y coordinates of nodes... | ||
722 | - if (debug('showNodeXY')) { | ||
723 | - node.select('circle').attr('fill-opacity', 0.5); | ||
724 | - node.append('circle') | ||
725 | - .attr({ | ||
726 | - class: 'debug', | ||
727 | - cx: 0, | ||
728 | - cy: 0, | ||
729 | - r: '3px' | ||
730 | - }); | ||
731 | - } | ||
732 | - }); | ||
733 | - | ||
734 | - // this function is scheduled to happen soon after the given thread ends | ||
735 | - setTimeout(function() { | ||
736 | - var lab = config.labels, | ||
737 | - portGap = lab.port.gap, | ||
738 | - midW = portGap + lab.port.width/ 2, | ||
739 | - midH = portGap + lab.port.height / 2; | ||
740 | - | ||
741 | - // post process the device nodes, to pad their size to fit the | ||
742 | - // label text and attach the icon to the right location. | ||
743 | - network.node.filter('.device').each(function(d) { | ||
744 | - // for every node, recompute size, padding, etc. so text fits | ||
745 | - var node = d3.select(this), | ||
746 | - text = node.select('text'), | ||
747 | - box = adjustRectToFitText(node); | ||
748 | - | ||
749 | - // now make the computed adjustment | ||
750 | - node.select('rect') | ||
751 | - .attr(box); | ||
752 | - | ||
753 | - node.select('image') | ||
754 | - .attr('x', box.x + config.icons.xoff) | ||
755 | - .attr('y', box.y + config.icons.yoff); | ||
756 | - | ||
757 | - var bounds = boundsFromBox(box), | ||
758 | - portBounds = { | ||
759 | - x1: bounds.x1 - midW, | ||
760 | - x2: bounds.x2 + midW, | ||
761 | - y1: bounds.y1 - midH, | ||
762 | - y2: bounds.y2 + midH | ||
763 | - }; | ||
764 | - | ||
765 | - // todo: clean up extent and edge work.. | ||
766 | - d.extent = { | ||
767 | - left: bounds.x1 - lab.marginLR, | ||
768 | - right: bounds.x2 + lab.marginLR, | ||
769 | - top: bounds.y1 - lab.marginTB, | ||
770 | - bottom: bounds.y2 + lab.marginTB | ||
771 | - }; | ||
772 | - | ||
773 | - d.edge = { | ||
774 | - left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2), | ||
775 | - right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2), | ||
776 | - top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1), | ||
777 | - bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) | ||
778 | - }; | ||
779 | - | ||
780 | - d.portEdge = { | ||
781 | - left : new geo.LineSegment( | ||
782 | - portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2 | ||
783 | - ), | ||
784 | - right : new geo.LineSegment( | ||
785 | - portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2 | ||
786 | - ), | ||
787 | - top : new geo.LineSegment( | ||
788 | - portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1 | ||
789 | - ), | ||
790 | - bottom : new geo.LineSegment( | ||
791 | - portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2 | ||
792 | - ) | ||
793 | - }; | ||
794 | - | ||
795 | - }); | ||
796 | - | ||
797 | - network.numTicks = 0; | ||
798 | - network.preventCollisions = false; | ||
799 | - network.force.start(); | ||
800 | - for (var i = 0; i < config.force.ticksWithoutCollisions; i++) { | ||
801 | - network.force.tick(); | ||
802 | - } | ||
803 | - network.preventCollisions = true; | ||
804 | - $('#view').css('visibility', 'visible'); | ||
805 | - }); | ||
806 | - | ||
807 | - | ||
808 | - // returns the newly computed bounding box of the rectangle | ||
809 | - function adjustRectToFitText(n) { | ||
810 | - var text = n.select('text'), | ||
811 | - box = text.node().getBBox(), | ||
812 | - lab = config.labels; | ||
813 | - | ||
814 | - // not sure why n.data() returns an array of 1 element... | ||
815 | - var data = n.data()[0]; | ||
816 | - | ||
817 | - text.attr('text-anchor', 'middle') | ||
818 | - .attr('y', '-0.8em') | ||
819 | - .attr('x', lab.imgPad/2) | ||
820 | - ; | ||
821 | - | ||
822 | - // translate the bbox so that it is centered on [x,y] | ||
823 | - box.x = -box.width / 2; | ||
824 | - box.y = -box.height / 2; | ||
825 | - | ||
826 | - // add padding | ||
827 | - box.x -= (lab.padLR + lab.imgPad/2); | ||
828 | - box.width += lab.padLR * 2 + lab.imgPad; | ||
829 | - box.y -= lab.padTB; | ||
830 | - box.height += lab.padTB * 2; | ||
831 | - | ||
832 | - return box; | ||
833 | - } | ||
834 | - | ||
835 | - function boundsFromBox(box) { | ||
836 | - return { | ||
837 | - x1: box.x, | ||
838 | - y1: box.y, | ||
839 | - x2: box.x + box.width, | ||
840 | - y2: box.y + box.height | ||
841 | - }; | ||
842 | - } | ||
843 | - | ||
844 | - } | ||
845 | - | ||
846 | - function iconUrl(d) { | ||
847 | - return 'img/' + d.type + '.png'; | ||
848 | -// return config.iconUrl[d.icon]; | ||
849 | - } | ||
850 | - | ||
851 | - function translate(x, y) { | ||
852 | - return 'translate(' + x + ',' + y + ')'; | ||
853 | - } | ||
854 | - | ||
855 | - // prevents collisions amongst device nodes | ||
856 | - function preventCollisions() { | ||
857 | - var quadtree = d3.geom.quadtree(network.nodes), | ||
858 | - hrad = config.hostRadius; | ||
859 | - | ||
860 | - network.nodes.forEach(function(n) { | ||
861 | - var nx1, nx2, ny1, ny2; | ||
862 | - | ||
863 | - if (n.class === 'device') { | ||
864 | - nx1 = n.x + n.extent.left; | ||
865 | - nx2 = n.x + n.extent.right; | ||
866 | - ny1 = n.y + n.extent.top; | ||
867 | - ny2 = n.y + n.extent.bottom; | ||
868 | - | ||
869 | - } else { | ||
870 | - nx1 = n.x - hrad; | ||
871 | - nx2 = n.x + hrad; | ||
872 | - ny1 = n.y - hrad; | ||
873 | - ny2 = n.y + hrad; | ||
874 | - } | ||
875 | - | ||
876 | - quadtree.visit(function(quad, x1, y1, x2, y2) { | ||
877 | - if (quad.point && quad.point !== n) { | ||
878 | - // check if the rectangles/circles intersect | ||
879 | - var p = quad.point, | ||
880 | - px1, px2, py1, py2, ix; | ||
881 | - | ||
882 | - if (p.class === 'device') { | ||
883 | - px1 = p.x + p.extent.left; | ||
884 | - px2 = p.x + p.extent.right; | ||
885 | - py1 = p.y + p.extent.top; | ||
886 | - py2 = p.y + p.extent.bottom; | ||
887 | - | ||
888 | - } else { | ||
889 | - px1 = p.x - hrad; | ||
890 | - px2 = p.x + hrad; | ||
891 | - py1 = p.y - hrad; | ||
892 | - py2 = p.y + hrad; | ||
893 | - } | ||
894 | - | ||
895 | - ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2); | ||
896 | - | ||
897 | - if (ix) { | ||
898 | - var xa1 = nx2 - px1, // shift n left , p right | ||
899 | - xa2 = px2 - nx1, // shift n right, p left | ||
900 | - ya1 = ny2 - py1, // shift n up , p down | ||
901 | - ya2 = py2 - ny1, // shift n down , p up | ||
902 | - adj = Math.min(xa1, xa2, ya1, ya2); | ||
903 | - | ||
904 | - if (adj == xa1) { | ||
905 | - n.x -= adj / 2; | ||
906 | - p.x += adj / 2; | ||
907 | - } else if (adj == xa2) { | ||
908 | - n.x += adj / 2; | ||
909 | - p.x -= adj / 2; | ||
910 | - } else if (adj == ya1) { | ||
911 | - n.y -= adj / 2; | ||
912 | - p.y += adj / 2; | ||
913 | - } else if (adj == ya2) { | ||
914 | - n.y += adj / 2; | ||
915 | - p.y -= adj / 2; | ||
916 | - } | ||
917 | - } | ||
918 | - return ix; | ||
919 | - } | ||
920 | - }); | ||
921 | - | ||
922 | - }); | ||
923 | - } | ||
924 | - | ||
925 | - function tick(e) { | ||
926 | - network.numTicks++; | ||
927 | - | ||
928 | - if (config.options.layering) { | ||
929 | - // adjust the y-coord of each node, based on y-pos constraints | ||
930 | - network.nodes.forEach(function (n) { | ||
931 | - var z = e.alpha * n.constraint.weight; | ||
932 | - if (!isNaN(n.constraint.y)) { | ||
933 | - n.y = (n.constraint.y * z + n.y * (1 - z)); | ||
934 | - } | ||
935 | - }); | ||
936 | - } | ||
937 | - | ||
938 | - if (config.options.collisionPrevention && network.preventCollisions) { | ||
939 | - preventCollisions(); | ||
940 | - } | ||
941 | - | ||
942 | - var portHalfW = config.labels.port.width / 2, | ||
943 | - portHalfH = config.labels.port.height / 2; | ||
944 | - | ||
945 | - // clip visualization of links at bounds of nodes... | ||
946 | - network.link.each(function(d) { | ||
947 | - var xs = d.source.x, | ||
948 | - ys = d.source.y, | ||
949 | - xt = d.target.x, | ||
950 | - yt = d.target.y, | ||
951 | - line = new geo.LineSegment(xs, ys, xt, yt), | ||
952 | - e, ix, | ||
953 | - exs, eys, ext, eyt, | ||
954 | - pxs, pys, pxt, pyt; | ||
955 | - | ||
956 | - if (d.class === 'host') { | ||
957 | - // no adjustment for source end of link, since hosts are dots | ||
958 | - exs = xs; | ||
959 | - eys = ys; | ||
960 | - | ||
961 | - } else { | ||
962 | - for (e in d.source.edge) { | ||
963 | - ix = line.intersect(d.source.edge[e].offset(xs, ys)); | ||
964 | - if (ix.in1 && ix.in2) { | ||
965 | - exs = ix.x; | ||
966 | - eys = ix.y; | ||
967 | - | ||
968 | - // also pick off the port label intersection | ||
969 | - ix = line.intersect(d.source.portEdge[e].offset(xs, ys)); | ||
970 | - pxs = ix.x; | ||
971 | - pys = ix.y; | ||
972 | - break; | ||
973 | - } | ||
974 | - } | ||
975 | - } | ||
976 | - | ||
977 | - for (e in d.target.edge) { | ||
978 | - ix = line.intersect(d.target.edge[e].offset(xt, yt)); | ||
979 | - if (ix.in1 && ix.in2) { | ||
980 | - ext = ix.x; | ||
981 | - eyt = ix.y; | ||
982 | - | ||
983 | - // also pick off the port label intersection | ||
984 | - ix = line.intersect(d.target.portEdge[e].offset(xt, yt)); | ||
985 | - pxt = ix.x; | ||
986 | - pyt = ix.y; | ||
987 | - break; | ||
988 | - } | ||
989 | - } | ||
990 | - | ||
991 | - // adjust the endpoints of the link's line to match rectangles | ||
992 | - var sid = safeId(d.id); | ||
993 | - d3.select(this) | ||
994 | - .attr('x1', exs) | ||
995 | - .attr('y1', eys) | ||
996 | - .attr('x2', ext) | ||
997 | - .attr('y2', eyt); | ||
998 | - | ||
999 | - d3.select('#srcPort-' + sid) | ||
1000 | - .attr('x', pxs - portHalfW) | ||
1001 | - .attr('y', pys - portHalfH); | ||
1002 | - | ||
1003 | - d3.select('#tgtPort-' + sid) | ||
1004 | - .attr('x', pxt - portHalfW) | ||
1005 | - .attr('y', pyt - portHalfH); | ||
1006 | - | ||
1007 | - // TODO: fit label rect to size of port number. | ||
1008 | - d3.select('#srcText-' + sid) | ||
1009 | - .attr('x', pxs - 5) | ||
1010 | - .attr('y', pys + 3); | ||
1011 | - | ||
1012 | - d3.select('#tgtText-' + sid) | ||
1013 | - .attr('x', pxt - 5) | ||
1014 | - .attr('y', pyt + 3); | ||
1015 | - | ||
1016 | - }); | ||
1017 | - | ||
1018 | - // position each node by translating the node (group) by x,y | ||
1019 | - network.node | ||
1020 | - .attr('transform', function(d) { | ||
1021 | - return translate(d.x, d.y); | ||
1022 | - }); | ||
1023 | - | ||
1024 | - } | ||
1025 | - | ||
1026 | - // $('#docs-close').on('click', function() { | ||
1027 | - // deselectObject(); | ||
1028 | - // return false; | ||
1029 | - // }); | ||
1030 | - | ||
1031 | - // $(document).on('click', '.select-object', function() { | ||
1032 | - // var obj = graph.data[$(this).data('name')]; | ||
1033 | - // if (obj) { | ||
1034 | - // selectObject(obj); | ||
1035 | - // } | ||
1036 | - // return false; | ||
1037 | - // }); | ||
1038 | - | ||
1039 | - function findNodeFromData(d) { | ||
1040 | - var el = null; | ||
1041 | - network.node.filter('.' + d.class).each(function(n) { | ||
1042 | - if (n.id === d.id) { | ||
1043 | - el = d3.select(this); | ||
1044 | - } | ||
1045 | - }); | ||
1046 | - return el; | ||
1047 | - } | ||
1048 | - | ||
1049 | - function selectObject(obj, el) { | ||
1050 | - var node; | ||
1051 | - if (el) { | ||
1052 | - node = d3.select(el); | ||
1053 | - } else { | ||
1054 | - network.node.each(function(d) { | ||
1055 | - if (d == obj) { | ||
1056 | - node = d3.select(el = this); | ||
1057 | - } | ||
1058 | - }); | ||
1059 | - } | ||
1060 | - if (!node) return; | ||
1061 | - | ||
1062 | - if (node.classed('selected')) { | ||
1063 | - deselectObject(); | ||
1064 | - flyinPane(null); | ||
1065 | - return; | ||
1066 | - } | ||
1067 | - deselectObject(false); | ||
1068 | - | ||
1069 | - selected = { | ||
1070 | - obj : obj, | ||
1071 | - el : el | ||
1072 | - }; | ||
1073 | - | ||
1074 | - node.classed('selected', true); | ||
1075 | - flyinPane(obj); | ||
1076 | - } | ||
1077 | - | ||
1078 | - function deselectObject(doResize) { | ||
1079 | - // Review: logic of 'resize(...)' function. | ||
1080 | - if (doResize || typeof doResize == 'undefined') { | ||
1081 | - resize(false); | ||
1082 | - } | ||
1083 | - | ||
1084 | - // deselect all nodes in the network... | ||
1085 | - network.node.classed('selected', false); | ||
1086 | - selected = {}; | ||
1087 | - flyinPane(null); | ||
1088 | - } | ||
1089 | - | ||
1090 | - function flyinPane(obj) { | ||
1091 | - var pane = d3.select('#flyout'), | ||
1092 | - url; | ||
1093 | - | ||
1094 | - if (obj) { | ||
1095 | - // go get details of the selected object from the server... | ||
1096 | - url = detailJsonUrl(obj.id); | ||
1097 | - d3.json(url, function (err, data) { | ||
1098 | - if (err) { | ||
1099 | - alert('Oops! Error reading JSON...\n\n' + | ||
1100 | - 'URL: ' + url + '\n\n' + | ||
1101 | - 'Error: ' + err.message); | ||
1102 | - return; | ||
1103 | - } | ||
1104 | -// console.log("JSON data... " + url); | ||
1105 | -// console.log(data); | ||
1106 | - | ||
1107 | - displayDetails(data, pane); | ||
1108 | - }); | ||
1109 | - | ||
1110 | - } else { | ||
1111 | - // hide pane | ||
1112 | - pane.transition().duration(750) | ||
1113 | - .style('right', '-320px') | ||
1114 | - .style('opacity', 0.0); | ||
1115 | - } | ||
1116 | - } | ||
1117 | - | ||
1118 | - function displayDetails(data, pane) { | ||
1119 | - $('#flyout').empty(); | ||
1120 | - | ||
1121 | - var title = pane.append("h2"), | ||
1122 | - table = pane.append("table"), | ||
1123 | - tbody = table.append("tbody"); | ||
1124 | - | ||
1125 | - $('<img src="img/' + data.type + '.png">').appendTo(title); | ||
1126 | - $('<span>').attr('class', 'icon').text(data.id).appendTo(title); | ||
1127 | - | ||
1128 | - | ||
1129 | - // TODO: consider using d3 data bind to TR/TD | ||
1130 | - | ||
1131 | - data.propOrder.forEach(function(p) { | ||
1132 | - if (p === '-') { | ||
1133 | - addSep(tbody); | ||
1134 | - } else { | ||
1135 | - addProp(tbody, p, data.props[p]); | ||
1136 | - } | ||
1137 | - }); | ||
1138 | - | ||
1139 | - function addSep(tbody) { | ||
1140 | - var tr = tbody.append('tr'); | ||
1141 | - $('<hr>').appendTo(tr.append('td').attr('colspan', 2)); | ||
1142 | - } | ||
1143 | - | ||
1144 | - function addProp(tbody, label, value) { | ||
1145 | - var tr = tbody.append('tr'); | ||
1146 | - | ||
1147 | - tr.append('td') | ||
1148 | - .attr('class', 'label') | ||
1149 | - .text(label + ' :'); | ||
1150 | - | ||
1151 | - tr.append('td') | ||
1152 | - .attr('class', 'value') | ||
1153 | - .text(value); | ||
1154 | - } | ||
1155 | - | ||
1156 | - // show pane | ||
1157 | - pane.transition().duration(750) | ||
1158 | - .style('right', '20px') | ||
1159 | - .style('opacity', 1.0); | ||
1160 | - } | ||
1161 | - | ||
1162 | - function highlightObject(obj) { | ||
1163 | - if (obj) { | ||
1164 | - if (obj != highlighted) { | ||
1165 | - // TODO set or clear "inactive" class on nodes, based on criteria | ||
1166 | - network.node.classed('inactive', function(d) { | ||
1167 | - // return (obj !== d && | ||
1168 | - // d.relation(obj.id)); | ||
1169 | - return (obj !== d); | ||
1170 | - }); | ||
1171 | - // TODO: same with links | ||
1172 | - network.link.classed('inactive', function(d) { | ||
1173 | - return (obj !== d.source && obj !== d.target); | ||
1174 | - }); | ||
1175 | - } | ||
1176 | - highlighted = obj; | ||
1177 | - } else { | ||
1178 | - if (highlighted) { | ||
1179 | - // clear the inactive flag (no longer suppressed visually) | ||
1180 | - network.node.classed('inactive', false); | ||
1181 | - network.link.classed('inactive', false); | ||
1182 | - } | ||
1183 | - highlighted = null; | ||
1184 | - | ||
1185 | - } | ||
1186 | - } | ||
1187 | - | ||
1188 | - function hoverObject(obj) { | ||
1189 | - if (obj) { | ||
1190 | - hovered = obj; | ||
1191 | - } else { | ||
1192 | - if (hovered) { | ||
1193 | - hovered = null; | ||
1194 | - } | ||
1195 | - } | ||
1196 | - } | ||
1197 | - | ||
1198 | - | ||
1199 | - function resize() { | ||
1200 | - view.height = window.innerHeight - config.mastHeight; | ||
1201 | - view.width = window.innerWidth; | ||
1202 | - $('#view') | ||
1203 | - .css('height', view.height + 'px') | ||
1204 | - .css('width', view.width + 'px'); | ||
1205 | - | ||
1206 | - network.forceWidth = view.width - config.force.marginLR; | ||
1207 | - network.forceHeight = view.height - config.force.marginTB; | ||
1208 | - } | ||
1209 | - | ||
1210 | - // ====================================================================== | ||
1211 | - // register with the UI framework | ||
1212 | 166 | ||
1213 | onos.ui.addView('topo', { | 167 | onos.ui.addView('topo', { |
1214 | - load: loadNetworkView | 168 | + preload: preload, |
169 | + load: load, | ||
170 | + resize: resize | ||
1215 | }); | 171 | }); |
1216 | 172 | ||
1217 | - | ||
1218 | }(ONOS)); | 173 | }(ONOS)); |
1219 | - | ... | ... |
-
Please register or login to post a comment