Simon Hunt

GUI -- Major Work-In-Progress

- Added dataLoadError to view token.
- Restructured code, viz:
(1) svg and force layout initialized in preload callback
(2) load callback initializes topo rendering
(3) subsequent data loads modify topo rendering
...@@ -407,7 +407,8 @@ ...@@ -407,7 +407,8 @@
407 height: this.height, 407 height: this.height,
408 uid: this.uid, 408 uid: this.uid,
409 setRadio: this.setRadio, 409 setRadio: this.setRadio,
410 - setKeys: this.setKeys 410 + setKeys: this.setKeys,
411 + dataLoadError: this.dataLoadError
411 } 412 }
412 }, 413 },
413 414
...@@ -498,6 +499,16 @@ ...@@ -498,6 +499,16 @@
498 499
499 uid: function (id) { 500 uid: function (id) {
500 return makeUid(this, id); 501 return makeUid(this, id);
502 + },
503 +
504 + // TODO : implement custom dialogs (don't use alerts)
505 +
506 + dataLoadError: function (err, url) {
507 + var msg = 'Data Load Error\n\n' +
508 + err.status + ' -- ' + err.statusText + '\n\n' +
509 + 'relative-url: "' + url + '"\n\n' +
510 + 'complete-url: "' + err.responseURL + '"';
511 + alert(msg);
501 } 512 }
502 513
503 // TODO: consider schedule, clearTimer, etc. 514 // TODO: consider schedule, clearTimer, etc.
......
...@@ -24,3 +24,6 @@ svg #topo-bg { ...@@ -24,3 +24,6 @@ svg #topo-bg {
24 opacity: 0.5; 24 opacity: 0.5;
25 } 25 }
26 26
27 +svg .node {
28 + fill: #03c;
29 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -56,12 +56,24 @@ ...@@ -56,12 +56,24 @@
56 opt: 'img/opt.png' 56 opt: 'img/opt.png'
57 }, 57 },
58 force: { 58 force: {
59 - marginLR: 20, 59 + note: 'node.class or link.class is used to differentiate',
60 - marginTB: 20, 60 + linkDistance: {
61 + infra: 200,
62 + host: 40
63 + },
64 + linkStrength: {
65 + infra: 1.0,
66 + host: 1.0
67 + },
68 + charge: {
69 + device: -400,
70 + host: -100
71 + },
72 + pad: 20,
61 translate: function() { 73 translate: function() {
62 return 'translate(' + 74 return 'translate(' +
63 - config.force.marginLR + ',' + 75 + config.force.pad + ',' +
64 - config.force.marginTB + ')'; 76 + config.force.pad + ')';
65 } 77 }
66 } 78 }
67 }; 79 };
...@@ -94,7 +106,11 @@ ...@@ -94,7 +106,11 @@
94 // D3 selections 106 // D3 selections
95 var svg, 107 var svg,
96 bgImg, 108 bgImg,
97 - topoG; 109 + topoG,
110 + nodeG,
111 + linkG,
112 + node,
113 + link;
98 114
99 // ============================== 115 // ==============================
100 // For Debugging / Development 116 // For Debugging / Development
...@@ -175,23 +191,146 @@ ...@@ -175,23 +191,146 @@
175 // ============================== 191 // ==============================
176 // Private functions 192 // Private functions
177 193
178 - // set the size of the given element to that of the view 194 + // set the size of the given element to that of the view (reduced if padded)
179 - function setSize(el, view) { 195 + function setSize(el, view, pad) {
196 + var padding = pad ? pad * 2 : 0;
180 el.attr({ 197 el.attr({
181 - width: view.width(), 198 + width: view.width() - padding,
182 - height: view.height() 199 + height: view.height() - padding
183 }); 200 });
184 } 201 }
185 202
186 -
187 function getNetworkData(view) { 203 function getNetworkData(view) {
188 var url = getTopoUrl(); 204 var url = getTopoUrl();
189 205
190 - // TODO ... 206 + console.log('Fetching JSON: ' + url);
207 + d3.json(url, function(err, data) {
208 + if (err) {
209 + view.dataLoadError(err, url);
210 + } else {
211 + network.data = data;
212 + drawNetwork(view);
213 + }
214 + });
215 + }
216 +
217 + function drawNetwork(view) {
218 + preprocessData(view);
219 + updateLayout(view);
220 + }
221 +
222 + function preprocessData(view) {
223 + var w = view.width(),
224 + h = view.height(),
225 + hDevice = h * 0.6,
226 + hHost = h * 0.3,
227 + data = network.data,
228 + deviceLayout = computeInitLayout(w, hDevice, data.devices.length),
229 + hostLayout = computeInitLayout(w, hHost, data.hosts.length);
230 +
231 + network.lookup = {};
232 + network.nodes = [];
233 + network.links = [];
234 + // we created new arrays, so need to set the refs in the force layout
235 + network.force.nodes(network.nodes);
236 + network.force.links(network.links);
237 +
238 + // let's just start with the nodes
239 +
240 + // note that both 'devices' and 'hosts' get mapped into the nodes array
241 + function makeNode(d, cls, layout) {
242 + var node = {
243 + id: d.id,
244 + labels: d.labels,
245 + class: cls,
246 + icon: cls,
247 + type: d.type,
248 + x: layout.x(),
249 + y: layout.y()
250 + };
251 + network.lookup[d.id] = node;
252 + network.nodes.push(node);
253 + }
254 +
255 + // first the devices...
256 + network.data.devices.forEach(function (d) {
257 + makeNode(d, 'device', deviceLayout);
258 + });
259 +
260 + // then the hosts...
261 + network.data.hosts.forEach(function (d) {
262 + makeNode(d, 'host', hostLayout);
263 + });
264 +
265 + // TODO: process links
266 + }
267 +
268 + function computeInitLayout(w, h, n) {
269 + var maxdw = 60,
270 + compdw, dw, ox, layout;
271 +
272 + if (n < 2) {
273 + layout = { ox: w/2, dw: 0 }
274 + } else {
275 + compdw = (0.8 * w) / (n - 1);
276 + dw = Math.min(maxdw, compdw);
277 + ox = w/2 - ((n - 1)/2 * dw);
278 + layout = { ox: ox, dw: dw }
279 + }
280 +
281 + layout.i = 0;
282 +
283 + layout.x = function () {
284 + var x = layout.ox + layout.i*layout.dw;
285 + layout.i++;
286 + return x;
287 + };
288 +
289 + layout.y = function () {
290 + return h;
291 + };
292 +
293 + return layout;
294 + }
295 +
296 + function linkId(d) {
297 + return d.source.id + '~' + d.target.id;
298 + }
299 +
300 + function nodeId(d) {
301 + return d.id;
302 + }
303 +
304 + function updateLayout(view) {
305 + link = link.data(network.force.links(), linkId);
306 + link.enter().append('line')
307 + .attr('class', 'link');
308 + link.exit().remove();
309 +
310 + node = node.data(network.force.nodes(), nodeId);
311 + node.enter().append('circle')
312 + .attr('id', function (d) { return 'nodeId-' + d.id; })
313 + .attr('class', function (d) { return 'node'; })
314 + .attr('r', 12);
191 315
316 + network.force.start();
192 } 317 }
193 318
194 319
320 + function tick() {
321 + node.attr({
322 + cx: function(d) { return d.x; },
323 + cy: function(d) { return d.y; }
324 + });
325 +
326 + link.attr({
327 + x1: function (d) { return d.source.x; },
328 + y1: function (d) { return d.source.y; },
329 + x2: function (d) { return d.target.x; },
330 + y2: function (d) { return d.target.y; }
331 + });
332 + }
333 +
195 // ============================== 334 // ==============================
196 // View life-cycle callbacks 335 // View life-cycle callbacks
197 336
...@@ -199,15 +338,15 @@ ...@@ -199,15 +338,15 @@
199 var w = view.width(), 338 var w = view.width(),
200 h = view.height(), 339 h = view.height(),
201 idBg = view.uid('bg'), 340 idBg = view.uid('bg'),
202 - showBg = config.options.showBackground ? 'visible' : 'hidden'; 341 + showBg = config.options.showBackground ? 'visible' : 'hidden',
342 + fcfg = config.force,
343 + fpad = fcfg.pad,
344 + forceDim = [w - 2*fpad, h - 2*fpad];
203 345
204 // NOTE: view.$div is a D3 selection of the view's div 346 // NOTE: view.$div is a D3 selection of the view's div
205 svg = view.$div.append('svg'); 347 svg = view.$div.append('svg');
206 setSize(svg, view); 348 setSize(svg, view);
207 349
208 - topoG = svg.append('g')
209 - .attr('transform', config.force.translate());
210 -
211 // load the background image 350 // load the background image
212 bgImg = svg.append('svg:image') 351 bgImg = svg.append('svg:image')
213 .attr({ 352 .attr({
...@@ -219,6 +358,28 @@ ...@@ -219,6 +358,28 @@
219 .style({ 358 .style({
220 visibility: showBg 359 visibility: showBg
221 }); 360 });
361 +
362 + // group for the topology
363 + topoG = svg.append('g')
364 + .attr('transform', fcfg.translate());
365 +
366 + // subgroups for links and nodes
367 + linkG = topoG.append('g').attr('id', 'links');
368 + nodeG = topoG.append('g').attr('id', 'nodes');
369 +
370 + // selection of nodes and links
371 + link = linkG.selectAll('.link');
372 + node = nodeG.selectAll('.node');
373 +
374 + // set up the force layout
375 + network.force = d3.layout.force()
376 + .size(forceDim)
377 + .nodes(network.nodes)
378 + .links(network.links)
379 + .charge(function (d) { return fcfg.charge[d.class]; })
380 + .linkDistance(function (d) { return fcfg.linkDistance[d.class]; })
381 + .linkStrength(function (d) { return fcfg.linkStrength[d.class]; })
382 + .on('tick', tick);
222 } 383 }
223 384
224 385
......