fanout logic for layout. almost done.
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 179000e..4efa334 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -18,7 +18,7 @@
#map path {
fill:none;
- stroke:#888;
+ stroke:#333;
stroke-width:2;
}
diff --git a/web/ons-demo/data/configuration.json b/web/ons-demo/data/configuration.json
index 783a4de..27d2393 100644
--- a/web/ons-demo/data/configuration.json
+++ b/web/ons-demo/data/configuration.json
@@ -40,34 +40,40 @@
},
"geo": {
"00:00:00:08:a2:08:f9:01": {
- "lat": 39,
- "lng": -98,
- "label": "LAX"
+ "lat": 41.891033,
+ "lng": -87.628326,
+ "label": "CHI",
+ "fanOutAngle": 180
},
"00:00:00:00:ba:5e:ba:11": {
- "lat": 0,
- "lng": 0,
- "label": ""
+ "lat": 47.611024,
+ "lng": -122.33242,
+ "label": "SEA",
+ "fanOutAngle": 270
},
"00:00:20:4e:7f:51:8a:35": {
- "lat": 0,
- "lng": 0,
- "label": ""
+ "lat": 33.758599,
+ "lng": -84.387360,
+ "label": "ATL",
+ "fanOutAngle": 0
},
"00:00:00:00:00:00:ba:12": {
- "lat": 0,
- "lng": 0,
- "label": ""
+ "lat": 41.225925,
+ "lng": -74.00528,
+ "label": "NYC",
+ "fanOutAngle": 135
},
"00:00:00:00:ba:5e:ba:13": {
- "lat": 0,
- "lng": 0,
- "label": ""
+ "lat": 37.901187,
+ "lng": -76.037163,
+ "label": "DC",
+ "fanOutAngle": 45
},
"00:00:00:16:97:08:9a:46": {
- "lat": 0,
- "lng": 0,
- "label": ""
+ "lat": 34.102708,
+ "lng": -118.238983,
+ "label": "LA",
+ "fanOutAngle": 300
}
}
}
\ No newline at end of file
diff --git a/web/ons-demo/js/map.js b/web/ons-demo/js/map.js
index 7915187..60c9561 100644
--- a/web/ons-demo/js/map.js
+++ b/web/ons-demo/js/map.js
@@ -4,8 +4,8 @@
(function () {
var projection = d3.geo.mercator()
- .center([82, 45])
- .scale(10000)
+ .center([82, 46])
+ .scale(8000)
.rotate([-180,0]);
function createMap(svg, cb) {
@@ -27,75 +27,291 @@
});
}
+/***************************************************************************************************
-var projection
+***************************************************************************************************/
+var switchMap;
+function makeSwitchMap() {
+ switchMap = {};
+ model.coreSwitches.forEach(function (s) {
+ switchMap[s.dpid] = s;
+ });
+ model.aggregationSwitches.forEach(function (s) {
+ switchMap[s.dpid] = s;
+ });
+ model.edgeSwitches.forEach(function (s) {
+ switchMap[s.dpid] = s;
+ });
+}
+
+/***************************************************************************************************
+create a map from edge->aggregation and aggreation->core switches
+***************************************************************************************************/
+var switchAssociations;
+function makeAssociations() {
+ switchAssociations = {};
+
+ var key;
+ for (key in model.configuration.association) {
+ var aggregationSwitches = model.configuration.association[key];
+ aggregationSwitches.forEach(function (s) {
+ switchAssociations[s] = key;
+ });
+ }
+}
+
+/***************************************************************************************************
+get the upstream switch. this only makes sense for aggregation and edge switches
+***************************************************************************************************/
+function getUpstream(dpid, className) {
+ if (className === 'aggregation') {
+ return switchAssociations[dpid];
+ } else if (className === 'edge') {
+ var aggregationDpid = dpid.split(':');
+ aggregationDpid[7] = '01'; // the last component of the agg switch is always '01'
+ return aggregationDpid.join(':');
+ }
+}
+
+
+
+/*****************a**********************************************************************************
+create a map to hold the fanout information for the switches
+***************************************************************************************************/
+var fanouts;
+function makeFanouts() {
+ fanouts = {};
+ model.coreSwitches.forEach(function (s) {
+ fanouts[s.dpid] = model.configuration.geo[s.dpid];
+ fanouts[s.dpid].count = 0;
+ });
+
+ model.aggregationSwitches.forEach(function (s) {
+ fanouts[s.dpid] = {count: 0};
+ var upstreamFanout = fanouts[getUpstream(s.dpid, 'aggregation')];
+ upstreamFanout.count += 1;
+ });
+
+ model.edgeSwitches.forEach(function (s) {
+ fanouts[s.dpid] = {};
+ var upstreamFanout = fanouts[getUpstream(s.dpid, 'edge')];
+ upstreamFanout.count += 1;
+ });
+}
+
+
+var projection;
+var switchLayer;
+var labelsLayer;
+var linksLayer;
createTopologyView = function (cb) {
var svg = createRootSVG();
- createMap(svg, cb);
+ createMap(svg, function () {
+ switchLayer = topology.append('g');
+ labelsLayer = topology.append('g');
+ linksLayer = topology.append('g');
+
+ cb();
+ });
}
-function makeSwitchesModel(switches) {
+function updateLinkLines() {
+
+ // key on link dpids since these will come/go during demo
+ var linkLines = linksLayer.selectAll('.link').data(links, function (d) {
+ return d['src-switch']+'->'+d['dst-switch'];
+ });
+
+ // add new links
+ linkLines.enter().append("svg:path").attr("class", "link");
+
+ linkLines.attr('id', function (d) {
+ return makeLinkKey(d);
+ }).attr("d", function (d) {
+ var src = d3.select(document.getElementById(d['src-switch']));
+ var dst = d3.select(document.getElementById(d['dst-switch']));
+
+ if (src.empty() || dst.empty()) {
+ return "M0,0";
+ }
+
+ var srcPt = document.querySelector('svg').createSVGPoint();
+ srcPt.x = src.attr('x');
+ srcPt.y = src.attr('y');
+
+ var dstPt = document.querySelector('svg').createSVGPoint();
+ dstPt.x = dst.attr('x');
+ dstPt.y = dst.attr('y');
+
+ var midPt = document.querySelector('svg').createSVGPoint();
+ midPt.x = (srcPt.x + dstPt.x)/2;
+ midPt.y = (srcPt.y + dstPt.y)/2;
+
+ return line([srcPt, midPt, dstPt]);
+ })
+ .attr("marker-mid", function(d) { return "url(#arrow)"; })
+ .classed('pending', function (d) {
+ return d.pending;
+ });
+
+ // remove old links
+ linkLines.exit().remove();
+}
+
+var fanOutAngles = {
+ aggregation: 90,
+ edge: 7
+}
+
+function makeSwitchesModel(switches, className) {
var switchesModel = [];
switches.forEach(function (s) {
+ var geo = model.configuration.geo[s.dpid];
+
+ var pos, label;
+ if (geo) {
+ pos = projection([geo.lng, geo.lat]);
+ label = geo.label;
+ } else {
+ var upstream = getUpstream(s.dpid, className);
+ if (upstream) {
+ var upstreamGeo = fanouts[upstream];
+ pos = projection([upstreamGeo.lng, upstreamGeo.lat]);
+
+ var fanOutAngle = upstreamGeo.fanOutAngle;
+ fanOutAngle -= (upstreamGeo.count - 1) * fanOutAngles[className]/2;
+
+ var angle = toRadians(fanOutAngle);
+ var xOff = Math.sin(angle) * widths[className] * 20;
+ var yOff = Math.cos(angle) * widths[className] * 20;
+
+ pos = [pos[0] + xOff, pos[1] + yOff];
+
+ var fakeGeo = projection.invert(pos);
+
+ var fanout = fanouts[s.dpid];
+ fanout.fanOutAngle = fanOutAngle;
+ fanout.lng = fakeGeo[0];
+ fanout.lat = fakeGeo[1];
+
+ upstreamGeo.fanOutAngle += fanOutAngles[className];
+
+ } else {
+ pos = projection([-98, 39]);
+ }
+ }
+
switchesModel.push({
dpid: s.dpid,
state: s.state,
- className: 'core',
+ className: className,
controller: s.controller,
- geo: model.configuration.geo[s.dpid]
+ label: label,
+ x: pos[0],
+ y: pos[1]
});
});
return switchesModel;
}
-drawTopology = function () {
+function switchEnter(d) {
+ var g = d3.select(this);
+ var width = widths[d.className];
+ g.append('svg:circle').attr('r', width)
+ .classed(d.className, true)
+ .attr('cx', d.x)
+ .attr('cy', d.y);
- // enter
- function switchEnter(s) {
- var g = d3.select(this);
-
- g.append('svg:circle').attr('r', widths.core);
- g.append('svg:text').text(s.geo.label).attr('transform', 'translate(' + widths.core + ' ' + widths.core + ')');
-
+ if (d.label) {
+ g.append('svg:text')
+ .text(d.label)
+ .attr('x', d.x + width)
+ .attr('y', d.y + width);
}
+}
- var coreSwitches = topology.selectAll('.core').data(makeSwitchesModel(model.coreSwitches))
+function switchesEnter(switches) {
+ return switchLayer.selectAll('g').data(switches, function (d) {
+ return d.dpid;
+ })
.enter()
- .append('svg:g')
- .attr("id", function (d) {
- return d.dpid;
- })
- .classed('core', true)
- .attr("transform", function(d) {
- if (d.geo) {
- return "translate(" + projection([d.geo.lng, d.geo.lat]) + ")";
- }
- })
- .each(switchEnter);
+ .append('svg:g')
+ .attr("id", function (d) {
+ return d.dpid;
+ })
+ .attr('x', function (d) {
+ return d.x;
+ })
+ .attr('y', function (d) {
+ return d.y;
+ })
+ .each(switchEnter);
+}
+
+// function labelsEnter(className, model) {
+// .enter().append("svg:g")
+// .classed('nolabel', true)
+// .attr("id", function (data) {
+// return data.dpid + '-label';
+// })
+// return topology.selectAll('.'+className).data(makeSwitchesModel(model))
+// .enter()
+// .append('svg:g')
+// .attr("id", function (d) {
+// return d.dpid;
+// })
+// .classed('core', true)
+// .attr('x', function (d) {
+// return d.x;
+// })
+// .attr('y', function (d) {
+// return d.y;
+// })
+// .each(switchEnter);
+// }
- // update
- coreSwitches
- .each(function (data) {
+function switchesUpdate(switches) {
+ switches.each(function (d) {
// if there's a pending state changed and then the state changes, clear the pending class
var circle = d3.select(this);
- if (data.state === 'ACTIVE' && circle.classed('inactive') ||
- data.state === 'INACTIVE' && circle.classed('active')) {
+ if (d.state === 'ACTIVE' && circle.classed('inactive') ||
+ d.state === 'INACTIVE' && circle.classed('active')) {
circle.classed('pending', false);
}
})
- .attr('class', function (data) {
- if (data.state === 'ACTIVE' && data.controller) {
- return data.className + ' active ' + controllerColorMap[data.controller];
+ .attr('class', function (d) {
+ if (d.state === 'ACTIVE' && d.controller) {
+ return d.className + ' active ' + controllerColorMap[d.controller];
} else {
- return data.className + ' inactive ' + 'colorInactive';
+ return d.className + ' inactive ' + 'colorInactive';
}
});
+}
+drawTopology = function () {
+
+ makeSwitchMap();
+ makeAssociations();
+ makeFanouts();
+
+ var coreSwitches = makeSwitchesModel(model.coreSwitches, 'core');
+ var aggregationSwitches = makeSwitchesModel(model.aggregationSwitches, 'aggregation');
+ var edgeSwitches = makeSwitchesModel(model.edgeSwitches, 'edge');
+
+ var switches = coreSwitches.concat(aggregationSwitches).concat(edgeSwitches);
+
+ switchesUpdate(switchesEnter(switches));
+
+
+
+
+
+ updateLinkLines();
}
})();
\ No newline at end of file
diff --git a/web/ons-demo/js/rings.js b/web/ons-demo/js/rings.js
index ab7be1d..29907f6 100644
--- a/web/ons-demo/js/rings.js
+++ b/web/ons-demo/js/rings.js
@@ -16,6 +16,52 @@
cb();
}
+function updateLinkLines() {
+
+ // key on link dpids since these will come/go during demo
+ var linkLines = d3.select('svg').selectAll('.link').data(links, function (d) {
+ return d['src-switch']+'->'+d['dst-switch'];
+ });
+
+ // add new links
+ linkLines.enter().append("svg:path").attr("class", "link");
+
+ linkLines.attr('id', function (d) {
+ return makeLinkKey(d);
+ }).attr("d", function (d) {
+ var src = d3.select(document.getElementById(d['src-switch']));
+ var dst = d3.select(document.getElementById(d['dst-switch']));
+
+ if (src.empty() || dst.empty()) {
+ return "M0,0";
+ }
+
+ var srcPt = document.querySelector('svg').createSVGPoint();
+ srcPt.x = src.attr('x');
+ srcPt.y = src.attr('y');
+ srcPt = srcPt.matrixTransform(src[0][0].getCTM());
+
+ var dstPt = document.querySelector('svg').createSVGPoint();
+ dstPt.x = dst.attr('x');
+ dstPt.y = dst.attr('y');
+ dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
+
+ var midPt = document.querySelector('svg').createSVGPoint();
+ midPt.x = (srcPt.x + dstPt.x)/2;
+ midPt.y = (srcPt.y + dstPt.y)/2;
+
+ return line([srcPt, midPt, dstPt]);
+ })
+ .attr("marker-mid", function(d) { return "url(#arrow)"; })
+ .classed('pending', function (d) {
+ return d.pending;
+ });
+
+ // remove old links
+ linkLines.exit().remove();
+}
+
+
function createRingTopologyModel(model) {
var rings = [{
radius: 3,
@@ -269,6 +315,8 @@
// switches should not change during operation of the ui so no
// rings.exit()
+
+ updateLinkLines();
}
})();
diff --git a/web/ons-demo/js/topology.js b/web/ons-demo/js/topology.js
index 1602abb..b932a1f 100644
--- a/web/ons-demo/js/topology.js
+++ b/web/ons-demo/js/topology.js
@@ -6,59 +6,11 @@
(function () {
-function updateLinkLines() {
-
- // key on link dpids since these will come/go during demo
- var linkLines = d3.select('svg').selectAll('.link').data(links, function (d) {
- return d['src-switch']+'->'+d['dst-switch'];
- });
-
- // add new links
- linkLines.enter().append("svg:path").attr("class", "link");
-
- linkLines.attr('id', function (d) {
- return makeLinkKey(d);
- }).attr("d", function (d) {
- var src = d3.select(document.getElementById(d['src-switch']));
- var dst = d3.select(document.getElementById(d['dst-switch']));
-
- var srcPt = document.querySelector('svg').createSVGPoint();
- srcPt.x = src.attr('x');
- srcPt.y = src.attr('y');
- srcPt = srcPt.matrixTransform(src[0][0].getCTM());
-
- var dstPt = document.querySelector('svg').createSVGPoint();
- dstPt.x = dst.attr('x');
- dstPt.y = dst.attr('y');
- dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
-
- var midPt = document.querySelector('svg').createSVGPoint();
- midPt.x = (srcPt.x + dstPt.x)/2;
- midPt.y = (srcPt.y + dstPt.y)/2;
-
- return line([srcPt, midPt, dstPt]);
- })
- .attr("marker-mid", function(d) { return "url(#arrow)"; })
- .classed('pending', function (d) {
- return d.pending;
- });
-
- // remove old links
- linkLines.exit().remove();
-}
-
updateTopology = function() {
/* currently rings.js and map.js can be included to define the topology display */
-
drawTopology();
-
-
- /* the remainder should work regardless of the topology display */
-
-// updateLinkLines();
-
// setup the mouseover behaviors
var allSwitches = d3.selectAll('.edge, .core, .aggregation');