blob: f860d9eafb7297cc6d08ed9571185809473b5742 [file] [log] [blame]
Matteo Scandolo219b1a72016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
4 angular.module('xos.serviceTopology')
Matteo Scandolo388795a2016-02-22 09:57:55 -08005 .service('LogicTopologyHelper', function($window, $log, $rootScope, lodash, serviceTopologyConfig, NodeDrawer){
Matteo Scandoloaf286372016-02-09 14:46:14 -08006
7 var diagonal, nodes, links, i = 0, svgWidth, svgHeight, layout;
8
9 const baseData = {
10 name: 'Router',
11 type: 'router',
12 children: [
13 {
14 name: 'WAN',
15 type: 'network',
16 children: [
17 {
18 name: 'Rack',
19 type: 'rack',
Matteo Scandolo35d53c82016-02-16 14:44:51 -080020 computeNodes: [],
Matteo Scandoloaf286372016-02-09 14:46:14 -080021 children: [
22 {
23 name: 'LAN',
24 type: 'network',
Matteo Scandolo7e67a9a2016-02-16 16:33:26 -080025 children: [{
26 name: 'Subscriber',
27 type: 'subscriber'
28 }] //subscribers goes here
Matteo Scandoloaf286372016-02-09 14:46:14 -080029 }
30 ]
31 }
32 ]
33 }
34 ]
35 };
36
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080037 /**
Matteo Scandolocc0db942016-02-11 17:37:08 -080038 * Calculate the horizontal position for each element.
39 * subsrcribers, devices and routers have the same fixed width 20
40 * network have a fixed width 104
41 * rack have a fixed width 105
42 * build and array of 6 elements representing the position of each element in the svg
43 * to equally space them
44 */
45
46 this.computeElementPosition = (svgWidth) => {
47
48 let xPos = [];
49
50 let totalElWidth = lodash.reduce(serviceTopologyConfig.elWidths, (el, val) => val + el, 0);
51
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080052 let remainingSpace = svgWidth - totalElWidth - (serviceTopologyConfig.widthMargin * 2);
Matteo Scandolocc0db942016-02-11 17:37:08 -080053
54 let step = remainingSpace / (serviceTopologyConfig.elWidths.length - 1);
55
56 lodash.forEach(serviceTopologyConfig.elWidths, (el, i) => {
57
58 // get half of the previous elements width
59 let previousElWidth = 0;
60 if(i !== 0){
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080061 previousElWidth = lodash.reduce(serviceTopologyConfig.elWidths.slice(0, i), (el, val) => val + el, 0);
Matteo Scandolocc0db942016-02-11 17:37:08 -080062 }
63
64 let elPos =
65 serviceTopologyConfig.widthMargin // right margin
66 + (step * i) // space between elements
67 + (el / 2) // this el width
68 + previousElWidth; // previous elements width
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080069
Matteo Scandolocc0db942016-02-11 17:37:08 -080070 xPos.push(svgWidth - elPos);
71 })
72
73 return xPos
74 };
75
76 /**
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080077 * from a nested data structure,
78 * create nodes and links for a D3 Tree Layout
79 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080080 const computeLayout = (data) => {
81 let nodes = layout.nodes(data);
82
83 // Normalize for fixed-depth.
Matteo Scandolocc0db942016-02-11 17:37:08 -080084 nodes.forEach((d) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -080085 // position the child node horizontally
Matteo Scandolocc0db942016-02-11 17:37:08 -080086 d.y = this.computeElementPosition(svgWidth)[d.depth];
Matteo Scandoloaf286372016-02-09 14:46:14 -080087 });
88
89 let links = layout.links(nodes);
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080090
Matteo Scandoloaf286372016-02-09 14:46:14 -080091 return [nodes, links];
92 };
93
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080094 /**
95 * Draw the containing group for any node or update the existing one
96 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080097 const drawNodes = (svg, nodes) => {
98 // Update the nodes…
99 var node = svg.selectAll('g.node')
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800100 .data(nodes, d => {
101 if(!angular.isString(d.d3Id)){
102 d.d3Id = `tree-${++i}`;
103 }
104 return d.d3Id;
105 });
Matteo Scandoloaf286372016-02-09 14:46:14 -0800106
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800107 // Enter any new nodes
Matteo Scandoloaf286372016-02-09 14:46:14 -0800108 var nodeEnter = node.enter().append('g')
109 .attr({
110 class: d => `node ${d.type}`,
111 transform: `translate(${svgWidth / 2}, ${svgHeight / 2})`
112 });
113
Matteo Scandolofd468582016-02-16 16:03:43 -0800114 // create Nodes
Matteo Scandolo79de20a2016-02-16 15:06:11 -0800115 NodeDrawer.addNetworks(node.filter('.network'));
116 NodeDrawer.addRack(node.filter('.rack'));
117 NodeDrawer.addPhisical(node.filter('.router'));
118 NodeDrawer.addPhisical(node.filter('.subscriber'));
119 NodeDrawer.addDevice(node.filter('.device'));
Matteo Scandoloaf286372016-02-09 14:46:14 -0800120
Matteo Scandolo388795a2016-02-22 09:57:55 -0800121 // add event listener to subscriber
122 node.filter('.subscriber')
123 .on('click', () => {
124 $rootScope.$emit('subscriber.modal.open');
125 });
126
Matteo Scandolofd468582016-02-16 16:03:43 -0800127 //update nodes
128 // TODO if data change, only update them
129 // NodeDrawer.updateRack(node.filter('.rack'));
130
Matteo Scandoloaf286372016-02-09 14:46:14 -0800131 // Transition nodes to their new position.
132 var nodeUpdate = node.transition()
133 .duration(serviceTopologyConfig.duration)
134 .attr({
135 'transform': d => `translate(${d.y},${d.x})`
136 });
137
138 // TODO handle node remove
139 };
140
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800141 /**
142 * Handle links in the tree layout
143 */
Matteo Scandoloaf286372016-02-09 14:46:14 -0800144 const drawLinks = (svg, links) => {
145
146 diagonal = d3.svg.diagonal()
147 .projection(d => [d.y, d.x]);
148
149 // Update the links…
150 var link = svg.selectAll('path.link')
151 .data(links, d => {
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800152 return d.target.d3Id
Matteo Scandoloaf286372016-02-09 14:46:14 -0800153 });
154
155 // Enter any new links at the parent's previous position.
156 link.enter().insert('path', 'g')
157 .attr('class', d => `link ${d.target.type}`)
158 .attr('d', function(d) {
159 var o = {x: svgHeight / 2, y: svgWidth / 2};
160 return diagonal({source: o, target: o});
161 });
162
163 // Transition links to their new position.
164 link.transition()
165 .duration(serviceTopologyConfig.duration)
166 .attr('d', diagonal);
167 };
168
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800169 /**
170 * Calculate the svg size and setup tree layout
171 */
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800172 this.setupTree = (svg) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -0800173
174
175 svgWidth = svg.node().getBoundingClientRect().width;
176 svgHeight = svg.node().getBoundingClientRect().height;
177
178 const width = svgWidth - (serviceTopologyConfig.widthMargin * 2);
179 const height = svgHeight - (serviceTopologyConfig.heightMargin * 2);
180
181 layout = d3.layout.tree()
182 .size([height, width]);
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800183 };
Matteo Scandoloaf286372016-02-09 14:46:14 -0800184
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800185 /**
186 * Update the tree layout
187 */
188
189 this.updateTree = (svg) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -0800190 // Compute the new tree layout.
191 [nodes, links] = computeLayout(baseData);
192
193 drawNodes(svg, nodes);
194 drawLinks(svg, links);
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800195 }
Matteo Scandoloaf286372016-02-09 14:46:14 -0800196
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800197 /**
198 * Add Subscribers to the tree
199 */
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800200 this.addSubscribers = (subscribers) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -0800201
Matteo Scandoloaf286372016-02-09 14:46:14 -0800202 subscribers.map((subscriber) => {
203 subscriber.children = subscriber.devices;
204 });
205
206 // add subscriber to data tree
207 baseData.children[0].children[0].children[0].children = subscribers;
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800208
209 return baseData;
210 };
Matteo Scandoloaf286372016-02-09 14:46:14 -0800211
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800212 /**
213 * Add compute nodes to the rack element
214 */
215
216 this.addComputeNodes = (computeNodes) => {
217 baseData.children[0].children[0].computeNodes = computeNodes;
Matteo Scandolo79de20a2016-02-16 15:06:11 -0800218 };
219
Matteo Scandolo51031482016-02-17 13:54:11 -0800220 this.getInstanceStatus = (instances) => {
Matteo Scandolo79de20a2016-02-16 15:06:11 -0800221
222 const computeNodes = baseData.children[0].children[0].computeNodes;
223
Matteo Scandolofd468582016-02-16 16:03:43 -0800224 // unselect all
225 computeNodes.map((node) => {
226 node.instances.map((instance) => {
227 instance.selected = false
228 return instance;
229 });
230 });
231
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800232 lodash.forEach(instances, (instance) => {
Matteo Scandolo51031482016-02-17 13:54:11 -0800233 computeNodes.map((node) => {
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800234 node.instances.map((d3instance) => {
235 if(d3instance.id === instance.id){
236 d3instance.selected = true;
Matteo Scandolo51031482016-02-17 13:54:11 -0800237 }
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800238 return d3instance;
Matteo Scandolo51031482016-02-17 13:54:11 -0800239 });
240 });
241 });
Matteo Scandolo79de20a2016-02-16 15:06:11 -0800242
Matteo Scandoloaf286372016-02-09 14:46:14 -0800243 }
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800244 });
245
246}());