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
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 427a23f..375fe6b 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -407,7 +407,8 @@
height: this.height,
uid: this.uid,
setRadio: this.setRadio,
- setKeys: this.setKeys
+ setKeys: this.setKeys,
+ dataLoadError: this.dataLoadError
}
},
@@ -498,6 +499,16 @@
uid: function (id) {
return makeUid(this, id);
+ },
+
+ // TODO : implement custom dialogs (don't use alerts)
+
+ dataLoadError: function (err, url) {
+ var msg = 'Data Load Error\n\n' +
+ err.status + ' -- ' + err.statusText + '\n\n' +
+ 'relative-url: "' + url + '"\n\n' +
+ 'complete-url: "' + err.responseURL + '"';
+ alert(msg);
}
// TODO: consider schedule, clearTimer, etc.
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 88fcd94..eee9244 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -24,3 +24,6 @@
opacity: 0.5;
}
+svg .node {
+ fill: #03c;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 7ac9adc..f5e1792 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -56,12 +56,24 @@
opt: 'img/opt.png'
},
force: {
- marginLR: 20,
- marginTB: 20,
+ note: 'node.class or link.class is used to differentiate',
+ linkDistance: {
+ infra: 200,
+ host: 40
+ },
+ linkStrength: {
+ infra: 1.0,
+ host: 1.0
+ },
+ charge: {
+ device: -400,
+ host: -100
+ },
+ pad: 20,
translate: function() {
return 'translate(' +
- config.force.marginLR + ',' +
- config.force.marginTB + ')';
+ config.force.pad + ',' +
+ config.force.pad + ')';
}
}
};
@@ -94,7 +106,11 @@
// D3 selections
var svg,
bgImg,
- topoG;
+ topoG,
+ nodeG,
+ linkG,
+ node,
+ link;
// ==============================
// For Debugging / Development
@@ -175,22 +191,145 @@
// ==============================
// Private functions
- // set the size of the given element to that of the view
- function setSize(el, view) {
+ // set the size of the given element to that of the view (reduced if padded)
+ function setSize(el, view, pad) {
+ var padding = pad ? pad * 2 : 0;
el.attr({
- width: view.width(),
- height: view.height()
+ width: view.width() - padding,
+ height: view.height() - padding
});
}
-
function getNetworkData(view) {
var url = getTopoUrl();
- // TODO ...
-
+ console.log('Fetching JSON: ' + url);
+ d3.json(url, function(err, data) {
+ if (err) {
+ view.dataLoadError(err, url);
+ } else {
+ network.data = data;
+ drawNetwork(view);
+ }
+ });
}
+ function drawNetwork(view) {
+ preprocessData(view);
+ updateLayout(view);
+ }
+
+ function preprocessData(view) {
+ var w = view.width(),
+ h = view.height(),
+ hDevice = h * 0.6,
+ hHost = h * 0.3,
+ data = network.data,
+ deviceLayout = computeInitLayout(w, hDevice, data.devices.length),
+ hostLayout = computeInitLayout(w, hHost, data.hosts.length);
+
+ network.lookup = {};
+ network.nodes = [];
+ network.links = [];
+ // we created new arrays, so need to set the refs in the force layout
+ network.force.nodes(network.nodes);
+ network.force.links(network.links);
+
+ // let's just start with the nodes
+
+ // note that both 'devices' and 'hosts' get mapped into the nodes array
+ function makeNode(d, cls, layout) {
+ var node = {
+ id: d.id,
+ labels: d.labels,
+ class: cls,
+ icon: cls,
+ type: d.type,
+ x: layout.x(),
+ y: layout.y()
+ };
+ network.lookup[d.id] = node;
+ network.nodes.push(node);
+ }
+
+ // first the devices...
+ network.data.devices.forEach(function (d) {
+ makeNode(d, 'device', deviceLayout);
+ });
+
+ // then the hosts...
+ network.data.hosts.forEach(function (d) {
+ makeNode(d, 'host', hostLayout);
+ });
+
+ // TODO: process links
+ }
+
+ function computeInitLayout(w, h, n) {
+ var maxdw = 60,
+ compdw, dw, ox, layout;
+
+ if (n < 2) {
+ layout = { ox: w/2, dw: 0 }
+ } else {
+ compdw = (0.8 * w) / (n - 1);
+ dw = Math.min(maxdw, compdw);
+ ox = w/2 - ((n - 1)/2 * dw);
+ layout = { ox: ox, dw: dw }
+ }
+
+ layout.i = 0;
+
+ layout.x = function () {
+ var x = layout.ox + layout.i*layout.dw;
+ layout.i++;
+ return x;
+ };
+
+ layout.y = function () {
+ return h;
+ };
+
+ return layout;
+ }
+
+ function linkId(d) {
+ return d.source.id + '~' + d.target.id;
+ }
+
+ function nodeId(d) {
+ return d.id;
+ }
+
+ function updateLayout(view) {
+ link = link.data(network.force.links(), linkId);
+ link.enter().append('line')
+ .attr('class', 'link');
+ link.exit().remove();
+
+ node = node.data(network.force.nodes(), nodeId);
+ node.enter().append('circle')
+ .attr('id', function (d) { return 'nodeId-' + d.id; })
+ .attr('class', function (d) { return 'node'; })
+ .attr('r', 12);
+
+ network.force.start();
+ }
+
+
+ function tick() {
+ node.attr({
+ cx: function(d) { return d.x; },
+ cy: function(d) { return d.y; }
+ });
+
+ link.attr({
+ x1: function (d) { return d.source.x; },
+ y1: function (d) { return d.source.y; },
+ x2: function (d) { return d.target.x; },
+ y2: function (d) { return d.target.y; }
+ });
+ }
// ==============================
// View life-cycle callbacks
@@ -199,15 +338,15 @@
var w = view.width(),
h = view.height(),
idBg = view.uid('bg'),
- showBg = config.options.showBackground ? 'visible' : 'hidden';
+ showBg = config.options.showBackground ? 'visible' : 'hidden',
+ fcfg = config.force,
+ fpad = fcfg.pad,
+ forceDim = [w - 2*fpad, h - 2*fpad];
// NOTE: view.$div is a D3 selection of the view's div
svg = view.$div.append('svg');
setSize(svg, view);
- topoG = svg.append('g')
- .attr('transform', config.force.translate());
-
// load the background image
bgImg = svg.append('svg:image')
.attr({
@@ -219,6 +358,28 @@
.style({
visibility: showBg
});
+
+ // group for the topology
+ topoG = svg.append('g')
+ .attr('transform', fcfg.translate());
+
+ // subgroups for links and nodes
+ linkG = topoG.append('g').attr('id', 'links');
+ nodeG = topoG.append('g').attr('id', 'nodes');
+
+ // selection of nodes and links
+ link = linkG.selectAll('.link');
+ node = nodeG.selectAll('.node');
+
+ // set up the force layout
+ network.force = d3.layout.force()
+ .size(forceDim)
+ .nodes(network.nodes)
+ .links(network.links)
+ .charge(function (d) { return fcfg.charge[d.class]; })
+ .linkDistance(function (d) { return fcfg.linkDistance[d.class]; })
+ .linkStrength(function (d) { return fcfg.linkStrength[d.class]; })
+ .on('tick', tick);
}