blob: 979d28bcdba7516e6a7ddd8cd9ac965252b26942 [file] [log] [blame]
Paul Greyson30636252013-04-09 15:22:04 -07001(function () {
2
Paul Greysonc090d142013-04-09 16:59:03 -07003createTopologyView = function (cb) {
Paul Greysone15c4392013-04-09 15:05:31 -07004
5 window.addEventListener('resize', function () {
6 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
7 // either that or is it possible to position the paths so they get the automatic transform as well?
8// updateTopology();
9 });
10
Paul Greyson981e8c22013-04-09 17:43:59 -070011 var svg = createRootSVG();
Paul Greysone15c4392013-04-09 15:05:31 -070012
Paul Greysonc090d142013-04-09 16:59:03 -070013 topology = svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
14 attr('id', 'viewbox').append('svg:g').attr('id', 'topology').attr('transform', 'translate(500 500)');
15
Paul Greysonbbd708b2013-04-09 22:37:31 -070016 flowLayer = d3.select('svg');
17
18 // hack to make the shared flow drawing code work
19 drawingRings = true;
20
Paul Greysonc090d142013-04-09 16:59:03 -070021 cb();
Paul Greysone15c4392013-04-09 15:05:31 -070022}
23
Paul Greyson4e348b92013-04-09 21:02:06 -070024function updateLinkLines() {
25
26 // key on link dpids since these will come/go during demo
27 var linkLines = d3.select('svg').selectAll('.link').data(links, function (d) {
28 return d['src-switch']+'->'+d['dst-switch'];
29 });
30
31 // add new links
32 linkLines.enter().append("svg:path").attr("class", "link");
33
34 linkLines.attr('id', function (d) {
35 return makeLinkKey(d);
36 }).attr("d", function (d) {
37 var src = d3.select(document.getElementById(d['src-switch']));
38 var dst = d3.select(document.getElementById(d['dst-switch']));
39
40 if (src.empty() || dst.empty()) {
41 return "M0,0";
42 }
43
44 var srcPt = document.querySelector('svg').createSVGPoint();
45 srcPt.x = src.attr('x');
46 srcPt.y = src.attr('y');
47 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
48
49 var dstPt = document.querySelector('svg').createSVGPoint();
50 dstPt.x = dst.attr('x');
51 dstPt.y = dst.attr('y');
52 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
53
54 var midPt = document.querySelector('svg').createSVGPoint();
55 midPt.x = (srcPt.x + dstPt.x)/2;
56 midPt.y = (srcPt.y + dstPt.y)/2;
57
58 return line([srcPt, midPt, dstPt]);
59 })
60 .attr("marker-mid", function(d) { return "url(#arrow)"; })
61 .classed('pending', function (d) {
62 return d.pending;
63 });
64
65 // remove old links
66 linkLines.exit().remove();
67}
68
69
Paul Greyson30636252013-04-09 15:22:04 -070070function createRingTopologyModel(model) {
Paul Greysone15c4392013-04-09 15:05:31 -070071 var rings = [{
72 radius: 3,
73 width: widths.edge,
74 switches: model.edgeSwitches,
75 className: 'edge',
76 angles: []
77 }, {
78 radius: 2.25,
79 width: widths.aggregation,
80 switches: model.aggregationSwitches,
81 className: 'aggregation',
82 angles: []
83 }, {
84 radius: 0.75,
85 width: widths.core,
86 switches: model.coreSwitches,
87 className: 'core',
88 angles: []
89 }];
90
91
92 var aggRanges = {};
93
94 // arrange edge switches at equal increments
95 var k = 360 / rings[0].switches.length;
96 rings[0].switches.forEach(function (s, i) {
97 var angle = k * i;
98
99 rings[0].angles[i] = angle;
100
101 // record the angle for the agg switch layout
102 var dpid = s.dpid.split(':');
103 dpid[7] = '01'; // the last component of the agg switch is always '01'
104 var aggdpid = dpid.join(':');
105 var aggRange = aggRanges[aggdpid];
106 if (!aggRange) {
107 aggRange = aggRanges[aggdpid] = {};
108 aggRange.min = aggRange.max = angle;
109 } else {
110 aggRange.max = angle;
111 }
112 });
113
114 // arrange aggregation switches to "fan out" to edge switches
115 k = 360 / rings[1].switches.length;
116 rings[1].switches.forEach(function (s, i) {
117// rings[1].angles[i] = k * i;
118 var range = aggRanges[s.dpid];
119
120 rings[1].angles[i] = (range.min + range.max)/2;
121 });
122
123 // find the association between core switches and aggregation switches
124 var aggregationSwitchMap = {};
125 model.aggregationSwitches.forEach(function (s, i) {
126 aggregationSwitchMap[s.dpid] = i;
127 });
128
129 // put core switches next to linked aggregation switches
130 k = 360 / rings[2].switches.length;
131 rings[2].switches.forEach(function (s, i) {
132// rings[2].angles[i] = k * i;
133 var associatedAggregationSwitches = model.configuration.association[s.dpid];
134 // TODO: go between if there are multiple
135 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
136
137 rings[2].angles[i] = rings[1].angles[index];
138 });
139
140 // TODO: construct this form initially rather than converting. it works better because
141 // it allows binding by dpid
142 var testRings = [];
143 rings.forEach(function (ring) {
144 var testRing = [];
145 ring.switches.forEach(function (s, i) {
146 var testSwitch = {
147 dpid: s.dpid,
148 state: s.state,
149 radius: ring.radius,
150 width: ring.width,
151 className: ring.className,
152 angle: ring.angles[i],
153 controller: s.controller
154 };
155 testRing.push(testSwitch);
156 });
157
158
159 testRings.push(testRing);
160 });
161
162
163// return rings;
164 return testRings;
Paul Greyson30636252013-04-09 15:22:04 -0700165}
166
167drawTopology = function () {
168 // DRAW THE SWITCHES
Paul Greysonc090d142013-04-09 16:59:03 -0700169 var rings = topology.selectAll('.ring').data(createRingTopologyModel(model));
Paul Greyson30636252013-04-09 15:22:04 -0700170
171 function ringEnter(data, i) {
172 if (!data.length) {
173 return;
174 }
175
176 // create the nodes
177 var nodes = d3.select(this).selectAll("g")
178 .data(data, function (data) {
179 return data.dpid;
180 })
181 .enter().append("svg:g")
182 .attr("id", function (data, i) {
183 return data.dpid;
184 })
185 .attr("transform", function(data, i) {
186 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
187 });
188
189 // add the cirles representing the switches
190 nodes.append("svg:circle")
191 .attr("transform", function(data, i) {
192 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
193 if (data.scale) {
194 m = m.scale(data.scale);
195 }
196 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
197 })
198 .attr("x", function (data) {
199 return -data.width / 2;
200 })
201 .attr("y", function (data) {
202 return -data.width / 2;
203 })
204 .attr("r", function (data) {
205 return data.width;
206 });
207 }
208
209 // append switches
210 rings.enter().append("svg:g")
211 .attr("class", "ring")
212 .each(ringEnter);
213
214
215 function ringUpdate(data, i) {
216 var nodes = d3.select(this).selectAll("g")
217 .data(data, function (data) {
218 return data.dpid;
219 });
220 nodes.select('circle')
221 .each(function (data) {
222 // if there's a pending state changed and then the state changes, clear the pending class
223 var circle = d3.select(this);
224 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
225 data.state === 'INACTIVE' && circle.classed('active')) {
226 circle.classed('pending', false);
227 }
228 })
229 .attr('class', function (data) {
230 if (data.state === 'ACTIVE' && data.controller) {
231 return data.className + ' active ' + controllerColorMap[data.controller];
232 } else {
233 return data.className + ' inactive ' + 'colorInactive';
234 }
235 });
236 }
237
238 // update switches
239 rings.each(ringUpdate);
240
241
242 // Now setup the labels
243 // This is done separately because SVG draws in node order and we want the labels
244 // always on top
Paul Greysonc090d142013-04-09 16:59:03 -0700245 var labelRings = topology.selectAll('.labelRing').data(createRingTopologyModel(model));
Paul Greyson30636252013-04-09 15:22:04 -0700246
247 function labelRingEnter(data) {
248 if (!data.length) {
249 return;
250 }
251
252 // create the nodes
253 var nodes = d3.select(this).selectAll("g")
254 .data(data, function (data) {
255 return data.dpid;
256 })
257 .enter().append("svg:g")
258 .classed('nolabel', true)
259 .attr("id", function (data) {
260 return data.dpid + '-label';
261 })
262 .attr("transform", function(data, i) {
263 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
264 })
265
266 // add the text nodes which show on mouse over
267 nodes.append("svg:text")
268 .text(function (data) {return data.dpid;})
269 .attr("x", function (data) {
270 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
271 if (data.className == 'edge') {
272 return - data.width*3 - 4;
273 } else {
274 return - data.width - 4;
275 }
276 } else {
277 if (data.className == 'edge') {
278 return data.width*3 + 4;
279 } else {
280 return data.width + 4;
281 }
282 }
283 })
284 .attr("y", function (data) {
285 var y;
286 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
287 if (data.className == 'edge') {
288 y = data.width*3/2 + 4;
289 } else {
290 y = data.width/2 + 4;
291 }
292 } else {
293 if (data.className == 'edge') {
294 y = data.width*3/2 + 4;
295 } else {
296 y = data.width/2 + 4;
297 }
298 }
299 return y - 6;
300 })
301 .attr("text-anchor", function (data) {
302 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
303 return "end";
304 } else {
305 return "start";
306 }
307 })
308 .attr("transform", function(data) {
309 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
310 if (data.scale) {
311 m = m.scale(data.scale);
312 }
313 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
314 })
315 }
316
317 labelRings.enter().append("svg:g")
318 .attr("class", "textRing")
319 .each(labelRingEnter);
320
321 // switches should not change during operation of the ui so no
322 // rings.exit()
Paul Greyson4e348b92013-04-09 21:02:06 -0700323
324 updateLinkLines();
Paul Greyson30636252013-04-09 15:22:04 -0700325}
326
327})();