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
Showing
3 changed files
with
191 additions
and
16 deletions
... | @@ -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. | ... | ... |
... | @@ -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 | ... | ... |
-
Please register or login to post a comment