blob: b91c8f47ce437652233ce4ce40c77b51f658a805 [file] [log] [blame]
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -07001(function () {
2 'use strict';
3
4 angular.module('xos.mcord-slicing')
5 .directive('slicingTopo', function(){
6 return {
7 restrict: 'E',
8 scope: {},
9 bindToController: true,
10 controllerAs: 'vm',
11 templateUrl: 'templates/slicing-topo.tpl.html',
12 controller: function($element, SliceGraph, McordSlicingTopo, _, NodePositioner, FormHandler){
13
14 let svg;
15 let nodes, links;
16 let nodeGroup, linkGroup, formGroup;
17 let dragLine, dragStartNode, dragEndNode, selectedLink;
18 let selectedNode, nodeSiblings;
19
20 var t = d3.transition()
21 .duration(500);
22
23 this.activeSlices = [];
24
25 const resetDragInfo = () => {
26 // reset drag nodes
27 dragStartNode = null;
28 dragEndNode = null;
29
30 // hide dragLine
31 dragLine
32 .classed('hidden', true);
33 };
34
35 McordSlicingTopo.query().$promise
36 .then((topology) => {
37 NodePositioner.storeEl($element[0]);
38 handleSvg($element[0]);
39 SliceGraph.buildGraph(topology);
40 _nodes = SliceGraph.positionGraph($element[0]);
41 _links = SliceGraph.getGraphLinks(_nodes);
42 drawGraph();
43 })
44 .catch((e) => {
45 throw new Error(e);
46 });
47
48 const handleSvg = (el) => {
49 this.el = el;
50 d3.select(el).select('svg').remove();
51
52 svg = d3.select(el)
53 .append('svg')
54 .style('width', `${el.clientWidth}px`)
55 .style('height', `${el.clientHeight}px`);
56
57 linkGroup = svg.append('g')
58 .attr({
59 class: 'link-group'
60 });
61
62 nodeGroup = svg.append('g')
63 .attr({
64 class: 'node-group'
65 });
66
67 formGroup = d3.select(el)
68 .append('div')
69 .attr({
70 class: 'form-container'
71 });
72
73 // line displayed when dragging nodes
74 dragLine = svg.append('svg:path')
75 .attr('class', 'dragline hidden')
76 .attr('d', 'M0,0L0,0');
77 };
78
79 const tick = () => {
80 svg.selectAll('.link')
81 .attr('x1', d => d.source.x)
82 .attr('y1', d => d.source.y)
83 .attr('x2', d => d.target.x)
84 .attr('y2', d => d.target.y);
85 };
86
87 // prepare the data to show all slices
88 let _nodes = [];
89 let _links = [];
90
91 // attach slice details
92 const attachSliceDetails = n => {
93 let [newNodes, newLinks] = SliceGraph.getSliceDetail(n);
94 _nodes = _nodes.concat(newNodes);
95 _links = _links.concat(newLinks);
96 drawGraph();
97 };
98
99 // remove slice detail
100 const removeSliceDetails = sliceId => {
101
102 SliceGraph.removeActiveSlice(sliceId);
103
104 // if the selected node is part of the slice I'm closing
105 // deselect the node
106 if(selectedNode && selectedNode.sliceId === sliceId){
107 selectedNode = null;
108 nodeSiblings = null;
109 }
110
111 // remove control plane nodes related to this slice
112 _nodes = _.filter(_nodes, n => {
113 if(n.sliceId === sliceId && (n.plane === 'control' || n.type === 'button')){
114 // if I remove the node check that there is no form attached
115 FormHandler.removeFormByParentNode(n, linkGroup, formGroup);
116 return false;
117 }
118 return true;
119 });
120
121 // remove sliceId from data plane element
122 _nodes = _.map(_nodes, n => {
123 if(n.sliceId === sliceId){
124 delete n.sliceId;
125 }
126 return n;
127 });
128
129 // remove control plane links related to this slice
130 _links = _.filter(_links, l => {
131 if(_.findIndex(_nodes, {id: l.data.source}) === -1){
132 return false;
133 }
134 if(_.findIndex(_nodes, {id: l.data.target}) === -1){
135 return false;
136 }
137 return true;
138 });
139 drawGraph();
140 };
141
142 const deleteLink = linkId => {
143 // TODO
144 // [ ] delete from graphlib
145 // [ ] delete from backend
146 console.log(_links);
147 _.remove(_links, (l) => {
148 return l.data.id === linkId;
149 });
150 console.log(_links);
151 drawGraph();
152 };
153
154 const expandNode = (n) => {
155 console.log('exp', n);
156 resetDragInfo();
157 const sliceComponents = ['ran-ru', 'ran-cu', 'pgw', 'sgw'];
158 if(sliceComponents.indexOf(n.type) > -1 && n.plane === 'data' && !n.sliceId){
159 attachSliceDetails(n);
160 }
161 else if (n.type === 'button'){
162 removeSliceDetails(n.sliceId);
163 }
164 else if (!n.formAttached && n.model){
165 n.formAttached = true;
166 FormHandler.drawForm(n, linkGroup, formGroup);
167 }
168 else if (n.formAttached){
169 n.formAttached = false;
170 FormHandler.removeFormByParentNode(n, linkGroup, formGroup);
171 }
172 };
173
174 const selectNextNode = () => {
175 if(!selectedNode){
176 selectedNode = _nodes[0];
177 selectedNode.selected = true;
178 }
179 else {
180 // TODO if no sliceId check only data plane successors
181 nodeSiblings = SliceGraph.getNodeSuccessors(selectedNode);
182
183 if(nodeSiblings.length === 0){
184 return;
185 };
186 // reset current selected node
187 selectedNode.selected = false;
188 // find next node
189 let nextNodeId = _.findIndex(_nodes, {id: nodeSiblings[0].id});
190 selectedNode = _nodes[nextNodeId];
191 selectedNode.selected = true;
192
193 // NOTE I need to update sibling with successor of the parent
194 // to enable vertical navigation
195 let parents = SliceGraph.getNodeSuccessors(selectedNode);
196 if(parents.lenght > 0){
197 nodeSiblings = SliceGraph.getNodePredecessors(parents[0]);
198 }
199 else {
200 nodeSiblings = null;
201 }
202 }
203 drawGraph();
204 };
205
206 const selectPrevNode = () => {
207 if(!selectedNode){
208 selectedNode = _nodes[0];
209 selectedNode.selected = true;
210 }
211 else {
212 nodeSiblings = SliceGraph.getNodePredecessors(selectedNode);
213
214 if(nodeSiblings.length === 0){
215 return;
216 };
217 // reset current selected node
218 selectedNode.selected = false;
219 // find next node
220 let prev = _.findIndex(_nodes, {id: nodeSiblings[0].id});
221
222 if(prev < 0){
223 prev = _nodes.length - 1;
224 }
225 selectedNode = _nodes[prev];
226 selectedNode.selected = true;
227 }
228 drawGraph();
229 };
230
231 const sortByY = (a, b) => {
232 if (a.y < b.y)
233 return 1;
234 if (a.y > b.y)
235 return -1;
236 return 0;
237 };
238
239 const getSameTypeNodes = (selectedNode) => {
240 return _.filter(_nodes, n => {
241 if(selectedNode.type === 'pgw' && n.type === 'button'){
242 return true;
243 }
244 if(selectedNode.type === 'button' && n.type === 'pgw'){
245 return true;
246 }
247 if (selectedNode.type === 'sgw' && n.type === 'mme'){
248 return true;
249 }
250 if (selectedNode.type === 'mme' && n.type === 'sgw'){
251 return true;
252 }
253 return n.type === selectedNode.type;
254 }).sort(sortByY);
255 };
256
257 const selectNextSibling = () => {
258 if(!selectedNode){
259 selectedNode = _nodes[0];
260 selectedNode.selected = true;
261 }
262 else {
263 // reset current selected node
264 selectedNode.selected = false;
265
266 // find next node
267 let sameTypeNodes = getSameTypeNodes(selectedNode);
268
269 let nextSiblingId = _.findIndex(sameTypeNodes, {id: selectedNode.id}) + 1;
270 if(nextSiblingId === sameTypeNodes.length){
271 nextSiblingId = 0;
272 }
273 let nextNodeId = _.findIndex(_nodes, {id: sameTypeNodes[nextSiblingId].id});
274 selectedNode = _nodes[nextNodeId];
275 selectedNode.selected = true;
276 }
277 drawGraph();
278 };
279
280 const selectPrevSibling = () => {
281 if(!selectedNode){
282 selectedNode = _nodes[0];
283 selectedNode.selected = true;
284 }
285 else {
286 // reset current selected node
287 selectedNode.selected = false;
288
289 // find next node
290 let sameTypeNodes = getSameTypeNodes(selectedNode);
291
292 let nextSiblingId = _.findIndex(sameTypeNodes, {id: selectedNode.id}) - 1;
293 if(nextSiblingId < 0){
294 nextSiblingId = sameTypeNodes.length - 1;
295 }
296 let nextNodeId = _.findIndex(_nodes, {id: sameTypeNodes[nextSiblingId].id});
297 selectedNode = _nodes[nextNodeId];
298 selectedNode.selected = true;
299 }
300 drawGraph();
301 };
302
303 const drawGraph = () => {
304
305 // svg.selectAll('.node-container').remove();
306 // svg.selectAll('.link-container').remove();
307
308 var force = d3.layout.force()
309 .nodes(_nodes)
310 .links(_links)
311 .charge(-1060)
312 .gravity(0.1)
313 .linkDistance(200)
314 .size([this.el.clientWidth, this.el.clientHeight])
315 .on('tick', tick)
316 .start();
317
318 links = linkGroup.selectAll('.link-container')
319 .data(_links, d => d.data.id)
320 .enter()
321 .insert('g')
322 .attr({
323 class: 'link-container',
324 opacity: 0
325 });
326
327 links
328 .transition(t)
329 .attr({
330 opacity: 1
331 });
332
333 links.insert('line')
334 .attr('class', d => `link ${d.data.plane}`)
335 .on('click', function(link ){
336 selectedLink = link;
337
338 // deselect all other links
339 d3.selectAll('.link').classed('selected', false);
340
341 d3.select(this).classed('selected', true);
342 });
343
344 nodes = nodeGroup.selectAll('.node')
345 .data(_nodes, d => d.id)
346 .attr({
347 class: d => `node ${d.plane} ${d.type} ${d.selected ? 'selected': ''}`,
348 });
349
350 nodes
351 .enter()
352 .append('g')
353 .attr({
354 class: 'node-container',
355 transform: d => d.transform,
356 opacity: 0
357 });
358
359 nodes.transition(t)
360 .attr({
361 opacity: 1
362 });
363
364 nodes.append('rect')
365 .attr({
366 class: d => `node ${d.plane} ${d.type} ${d.selected ? 'selected': ''}`,
367 width: 100,
368 height: 50,
369 x: -50,
370 y: -25
371 });
372
373 nodes.append('text')
374 .attr({
375 'text-anchor': 'middle',
376 'alignment-baseline': 'middle'
377 })
378 // .text(d => `${d.id} ${d.name}`);
379 .text(d => `${d.name}`);
380
381 nodes.on('click', (n) => {
382 expandNode(n);
383 });
384
385 nodes
386 .on('mousedown', (n) => {
387 // save a reference to dragStart
388 dragStartNode = n;
389
390 dragLine
391 .classed('hidden', false)
392 .attr('d', 'M' + dragStartNode.x + ',' + dragStartNode.y + 'L' + dragStartNode.x + ',' + dragStartNode.y);
393 })
394 .on('mouseover', (n) => {
395 if(dragStartNode){
396 dragEndNode = n;
397 }
398 });
399
400 svg
401 .on('mousemove', function(){
402 if(!dragStartNode){
403 return;
404 }
405 dragLine.attr('d', 'M' + dragStartNode.x + ',' + dragStartNode.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
406 })
407 .on('mouseup', () => {
408 if(!dragStartNode || !dragEndNode){
409 resetDragInfo();
410 return;
411 }
412
413 // TODO
414 // [X] check that I can connect the two nodes
415 // [X] check link direction
416 // [ ] save the new link in the BE
417
418 // check that I can connect the 2 nodes
419 const successorType = SliceGraph.getNodeDataPlaneSuccessors(dragStartNode)[0].type;
420 if(dragEndNode.type !== successorType){
421 resetDragInfo();
422 return;
423 }
424
425 // create the link
426 _links.push({
427 source: dragStartNode,
428 target: dragEndNode,
429 data: {
430 id: `${dragStartNode.id}.${dragEndNode.id}`,
431 source: dragStartNode.id,
432 target: dragEndNode.id
433 }
434 });
435
436 // update the graph
437 // TODO recalculate graph positions
438 drawGraph();
439
440 resetDragInfo();
441 });
442
443 // remove exiting nodes
444 svg.selectAll('.node-container')
445 .data(_nodes, d => d.id)
446 .exit()
447 .transition(t)
448 .attr({
449 opacity: 0
450 })
451 .remove();
452
453 // remove exiting links
454 svg.selectAll('.link-container')
455 .data(_links, d => d.data.id)
456 .exit()
457 .transition(t)
458 .attr({
459 opacity: 0
460 })
461 .remove();
462 };
463
464 d3.select('body')
465 .on('keydown', function(){
466 // console.log(d3.event.code);
467 if(d3.event.code === 'Backspace' && selectedLink){
468 // delete link
469 deleteLink(selectedLink.data.id);
470 }
471 if(d3.event.code === 'Enter' && selectedNode){
472 d3.event.preventDefault();
473 expandNode(selectedNode);
474 }
475 if(d3.event.code === 'Escape' && selectedNode){
476 selectedNode.selected = false;
477 selectedNode = null;
478 nodeSiblings = null;
479 drawGraph();
480 }
481 if(d3.event.code === 'ArrowRight'){
482 d3.event.preventDefault();
483 selectNextNode();
484 }
485 if(d3.event.code === 'ArrowLeft'){
486 d3.event.preventDefault();
487 selectPrevNode();
488 }
489 if(d3.event.code === 'ArrowUp'){
490 d3.event.preventDefault();
491 selectNextSibling();
492 }
493 if(d3.event.code === 'ArrowDown'){
494 d3.event.preventDefault();
495 selectPrevSibling();
496 }
497
498 });
499 }
500 }
501 });
502})();