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