blob: b0184efe0b351ede30f219a1c313d87e3948da86 [file] [log] [blame]
Matteo Scandolo219b1a72016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
4 angular.module('xos.serviceTopology')
Matteo Scandolo012dddb2016-02-22 16:53:22 -08005 .service('LogicTopologyHelper', function($window, $log, $rootScope, lodash, serviceTopologyConfig, NodeDrawer, ChartData){
Matteo Scandoloaf286372016-02-09 14:46:14 -08006
7 var diagonal, nodes, links, i = 0, svgWidth, svgHeight, layout;
8
Matteo Scandolo012dddb2016-02-22 16:53:22 -08009 const baseData = ChartData.logicTopologyData;
Matteo Scandoloaf286372016-02-09 14:46:14 -080010
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080011 /**
Matteo Scandolocc0db942016-02-11 17:37:08 -080012 * Calculate the horizontal position for each element.
13 * subsrcribers, devices and routers have the same fixed width 20
14 * network have a fixed width 104
15 * rack have a fixed width 105
16 * build and array of 6 elements representing the position of each element in the svg
17 * to equally space them
18 */
19
20 this.computeElementPosition = (svgWidth) => {
21
22 let xPos = [];
23
24 let totalElWidth = lodash.reduce(serviceTopologyConfig.elWidths, (el, val) => val + el, 0);
25
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080026 let remainingSpace = svgWidth - totalElWidth - (serviceTopologyConfig.widthMargin * 2);
Matteo Scandolocc0db942016-02-11 17:37:08 -080027
28 let step = remainingSpace / (serviceTopologyConfig.elWidths.length - 1);
29
30 lodash.forEach(serviceTopologyConfig.elWidths, (el, i) => {
31
32 // get half of the previous elements width
33 let previousElWidth = 0;
34 if(i !== 0){
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080035 previousElWidth = lodash.reduce(serviceTopologyConfig.elWidths.slice(0, i), (el, val) => val + el, 0);
Matteo Scandolocc0db942016-02-11 17:37:08 -080036 }
37
38 let elPos =
39 serviceTopologyConfig.widthMargin // right margin
40 + (step * i) // space between elements
41 + (el / 2) // this el width
42 + previousElWidth; // previous elements width
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080043
Matteo Scandolocc0db942016-02-11 17:37:08 -080044 xPos.push(svgWidth - elPos);
45 })
46
47 return xPos
48 };
49
50 /**
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080051 * from a nested data structure,
52 * create nodes and links for a D3 Tree Layout
53 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080054 const computeLayout = (data) => {
55 let nodes = layout.nodes(data);
56
57 // Normalize for fixed-depth.
Matteo Scandolocc0db942016-02-11 17:37:08 -080058 nodes.forEach((d) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -080059 // position the child node horizontally
Matteo Scandolocc0db942016-02-11 17:37:08 -080060 d.y = this.computeElementPosition(svgWidth)[d.depth];
Matteo Scandoloaf286372016-02-09 14:46:14 -080061 });
62
63 let links = layout.links(nodes);
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080064
Matteo Scandoloaf286372016-02-09 14:46:14 -080065 return [nodes, links];
66 };
67
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080068 /**
69 * Draw the containing group for any node or update the existing one
70 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080071 const drawNodes = (svg, nodes) => {
72 // Update the nodes…
73 var node = svg.selectAll('g.node')
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080074 .data(nodes, d => {
75 if(!angular.isString(d.d3Id)){
76 d.d3Id = `tree-${++i}`;
77 }
78 return d.d3Id;
79 });
Matteo Scandoloaf286372016-02-09 14:46:14 -080080
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080081 // Enter any new nodes
Matteo Scandoloaf286372016-02-09 14:46:14 -080082 var nodeEnter = node.enter().append('g')
83 .attr({
84 class: d => `node ${d.type}`,
85 transform: `translate(${svgWidth / 2}, ${svgHeight / 2})`
86 });
87
Matteo Scandolofd468582016-02-16 16:03:43 -080088 // create Nodes
Matteo Scandolo79de20a2016-02-16 15:06:11 -080089 NodeDrawer.addNetworks(node.filter('.network'));
90 NodeDrawer.addRack(node.filter('.rack'));
91 NodeDrawer.addPhisical(node.filter('.router'));
92 NodeDrawer.addPhisical(node.filter('.subscriber'));
93 NodeDrawer.addDevice(node.filter('.device'));
Matteo Scandoloaf286372016-02-09 14:46:14 -080094
Matteo Scandolo388795a2016-02-22 09:57:55 -080095 // add event listener to subscriber
96 node.filter('.subscriber')
97 .on('click', () => {
98 $rootScope.$emit('subscriber.modal.open');
99 });
100
Matteo Scandolofd468582016-02-16 16:03:43 -0800101 //update nodes
102 // TODO if data change, only update them
103 // NodeDrawer.updateRack(node.filter('.rack'));
104
Matteo Scandoloaf286372016-02-09 14:46:14 -0800105 // Transition nodes to their new position.
106 var nodeUpdate = node.transition()
107 .duration(serviceTopologyConfig.duration)
108 .attr({
109 'transform': d => `translate(${d.y},${d.x})`
110 });
111
112 // TODO handle node remove
Matteo Scandolo45fba732016-02-22 14:53:44 -0800113 var nodeExit = node.exit().remove();
Matteo Scandoloaf286372016-02-09 14:46:14 -0800114 };
115
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800116 /**
117 * Handle links in the tree layout
118 */
Matteo Scandoloaf286372016-02-09 14:46:14 -0800119 const drawLinks = (svg, links) => {
120
121 diagonal = d3.svg.diagonal()
122 .projection(d => [d.y, d.x]);
123
124 // Update the links…
125 var link = svg.selectAll('path.link')
126 .data(links, d => {
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800127 return d.target.d3Id
Matteo Scandoloaf286372016-02-09 14:46:14 -0800128 });
129
130 // Enter any new links at the parent's previous position.
131 link.enter().insert('path', 'g')
132 .attr('class', d => `link ${d.target.type}`)
133 .attr('d', function(d) {
134 var o = {x: svgHeight / 2, y: svgWidth / 2};
135 return diagonal({source: o, target: o});
136 });
137
138 // Transition links to their new position.
139 link.transition()
140 .duration(serviceTopologyConfig.duration)
141 .attr('d', diagonal);
Matteo Scandolo45fba732016-02-22 14:53:44 -0800142
143 link.exit().remove();
Matteo Scandoloaf286372016-02-09 14:46:14 -0800144 };
145
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800146 /**
147 * Calculate the svg size and setup tree layout
148 */
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800149 this.setupTree = (svg) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -0800150
151
152 svgWidth = svg.node().getBoundingClientRect().width;
153 svgHeight = svg.node().getBoundingClientRect().height;
154
155 const width = svgWidth - (serviceTopologyConfig.widthMargin * 2);
156 const height = svgHeight - (serviceTopologyConfig.heightMargin * 2);
157
158 layout = d3.layout.tree()
159 .size([height, width]);
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800160 };
Matteo Scandoloaf286372016-02-09 14:46:14 -0800161
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800162 /**
163 * Update the tree layout
164 */
165
166 this.updateTree = (svg) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -0800167 // Compute the new tree layout.
168 [nodes, links] = computeLayout(baseData);
169
Matteo Scandolo45fba732016-02-22 14:53:44 -0800170 // console.log(baseData);
Matteo Scandoloaf286372016-02-09 14:46:14 -0800171 drawNodes(svg, nodes);
172 drawLinks(svg, links);
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800173 }
Matteo Scandoloaf286372016-02-09 14:46:14 -0800174
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800175 });
176
177}());