blob: 73f7e0ed6ac606f1f728c35e9d4a6a72e91aa35f [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
11 var svg = d3.select('#svg-container').append('svg:svg');
12
13 svg.append("svg:defs").append("svg:marker")
14 .attr("id", "arrow")
15 .attr("viewBox", "0 -5 10 10")
16 .attr("refX", -1)
17 .attr("markerWidth", 5)
18 .attr("markerHeight", 5)
19 .attr("orient", "auto")
20 .append("svg:path")
21 .attr("d", "M0,-3L10,0L0,3");
22
Paul Greysonc090d142013-04-09 16:59:03 -070023 topology = svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
24 attr('id', 'viewbox').append('svg:g').attr('id', 'topology').attr('transform', 'translate(500 500)');
25
26 cb();
Paul Greysone15c4392013-04-09 15:05:31 -070027}
28
Paul Greyson30636252013-04-09 15:22:04 -070029function createRingTopologyModel(model) {
Paul Greysone15c4392013-04-09 15:05:31 -070030 var rings = [{
31 radius: 3,
32 width: widths.edge,
33 switches: model.edgeSwitches,
34 className: 'edge',
35 angles: []
36 }, {
37 radius: 2.25,
38 width: widths.aggregation,
39 switches: model.aggregationSwitches,
40 className: 'aggregation',
41 angles: []
42 }, {
43 radius: 0.75,
44 width: widths.core,
45 switches: model.coreSwitches,
46 className: 'core',
47 angles: []
48 }];
49
50
51 var aggRanges = {};
52
53 // arrange edge switches at equal increments
54 var k = 360 / rings[0].switches.length;
55 rings[0].switches.forEach(function (s, i) {
56 var angle = k * i;
57
58 rings[0].angles[i] = angle;
59
60 // record the angle for the agg switch layout
61 var dpid = s.dpid.split(':');
62 dpid[7] = '01'; // the last component of the agg switch is always '01'
63 var aggdpid = dpid.join(':');
64 var aggRange = aggRanges[aggdpid];
65 if (!aggRange) {
66 aggRange = aggRanges[aggdpid] = {};
67 aggRange.min = aggRange.max = angle;
68 } else {
69 aggRange.max = angle;
70 }
71 });
72
73 // arrange aggregation switches to "fan out" to edge switches
74 k = 360 / rings[1].switches.length;
75 rings[1].switches.forEach(function (s, i) {
76// rings[1].angles[i] = k * i;
77 var range = aggRanges[s.dpid];
78
79 rings[1].angles[i] = (range.min + range.max)/2;
80 });
81
82 // find the association between core switches and aggregation switches
83 var aggregationSwitchMap = {};
84 model.aggregationSwitches.forEach(function (s, i) {
85 aggregationSwitchMap[s.dpid] = i;
86 });
87
88 // put core switches next to linked aggregation switches
89 k = 360 / rings[2].switches.length;
90 rings[2].switches.forEach(function (s, i) {
91// rings[2].angles[i] = k * i;
92 var associatedAggregationSwitches = model.configuration.association[s.dpid];
93 // TODO: go between if there are multiple
94 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
95
96 rings[2].angles[i] = rings[1].angles[index];
97 });
98
99 // TODO: construct this form initially rather than converting. it works better because
100 // it allows binding by dpid
101 var testRings = [];
102 rings.forEach(function (ring) {
103 var testRing = [];
104 ring.switches.forEach(function (s, i) {
105 var testSwitch = {
106 dpid: s.dpid,
107 state: s.state,
108 radius: ring.radius,
109 width: ring.width,
110 className: ring.className,
111 angle: ring.angles[i],
112 controller: s.controller
113 };
114 testRing.push(testSwitch);
115 });
116
117
118 testRings.push(testRing);
119 });
120
121
122// return rings;
123 return testRings;
Paul Greyson30636252013-04-09 15:22:04 -0700124}
125
126drawTopology = function () {
127 // DRAW THE SWITCHES
Paul Greysonc090d142013-04-09 16:59:03 -0700128 var rings = topology.selectAll('.ring').data(createRingTopologyModel(model));
Paul Greyson30636252013-04-09 15:22:04 -0700129
130 function ringEnter(data, i) {
131 if (!data.length) {
132 return;
133 }
134
135 // create the nodes
136 var nodes = d3.select(this).selectAll("g")
137 .data(data, function (data) {
138 return data.dpid;
139 })
140 .enter().append("svg:g")
141 .attr("id", function (data, i) {
142 return data.dpid;
143 })
144 .attr("transform", function(data, i) {
145 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
146 });
147
148 // add the cirles representing the switches
149 nodes.append("svg:circle")
150 .attr("transform", function(data, i) {
151 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
152 if (data.scale) {
153 m = m.scale(data.scale);
154 }
155 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
156 })
157 .attr("x", function (data) {
158 return -data.width / 2;
159 })
160 .attr("y", function (data) {
161 return -data.width / 2;
162 })
163 .attr("r", function (data) {
164 return data.width;
165 });
166 }
167
168 // append switches
169 rings.enter().append("svg:g")
170 .attr("class", "ring")
171 .each(ringEnter);
172
173
174 function ringUpdate(data, i) {
175 var nodes = d3.select(this).selectAll("g")
176 .data(data, function (data) {
177 return data.dpid;
178 });
179 nodes.select('circle')
180 .each(function (data) {
181 // if there's a pending state changed and then the state changes, clear the pending class
182 var circle = d3.select(this);
183 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
184 data.state === 'INACTIVE' && circle.classed('active')) {
185 circle.classed('pending', false);
186 }
187 })
188 .attr('class', function (data) {
189 if (data.state === 'ACTIVE' && data.controller) {
190 return data.className + ' active ' + controllerColorMap[data.controller];
191 } else {
192 return data.className + ' inactive ' + 'colorInactive';
193 }
194 });
195 }
196
197 // update switches
198 rings.each(ringUpdate);
199
200
201 // Now setup the labels
202 // This is done separately because SVG draws in node order and we want the labels
203 // always on top
Paul Greysonc090d142013-04-09 16:59:03 -0700204 var labelRings = topology.selectAll('.labelRing').data(createRingTopologyModel(model));
Paul Greyson30636252013-04-09 15:22:04 -0700205
206 function labelRingEnter(data) {
207 if (!data.length) {
208 return;
209 }
210
211 // create the nodes
212 var nodes = d3.select(this).selectAll("g")
213 .data(data, function (data) {
214 return data.dpid;
215 })
216 .enter().append("svg:g")
217 .classed('nolabel', true)
218 .attr("id", function (data) {
219 return data.dpid + '-label';
220 })
221 .attr("transform", function(data, i) {
222 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
223 })
224
225 // add the text nodes which show on mouse over
226 nodes.append("svg:text")
227 .text(function (data) {return data.dpid;})
228 .attr("x", function (data) {
229 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
230 if (data.className == 'edge') {
231 return - data.width*3 - 4;
232 } else {
233 return - data.width - 4;
234 }
235 } else {
236 if (data.className == 'edge') {
237 return data.width*3 + 4;
238 } else {
239 return data.width + 4;
240 }
241 }
242 })
243 .attr("y", function (data) {
244 var y;
245 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
246 if (data.className == 'edge') {
247 y = data.width*3/2 + 4;
248 } else {
249 y = data.width/2 + 4;
250 }
251 } else {
252 if (data.className == 'edge') {
253 y = data.width*3/2 + 4;
254 } else {
255 y = data.width/2 + 4;
256 }
257 }
258 return y - 6;
259 })
260 .attr("text-anchor", function (data) {
261 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
262 return "end";
263 } else {
264 return "start";
265 }
266 })
267 .attr("transform", function(data) {
268 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
269 if (data.scale) {
270 m = m.scale(data.scale);
271 }
272 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
273 })
274 }
275
276 labelRings.enter().append("svg:g")
277 .attr("class", "textRing")
278 .each(labelRingEnter);
279
280 // switches should not change during operation of the ui so no
281 // rings.exit()
282}
283
284})();