blob: 8ba9c7166dfaddd6973c1100658a1f242671d4b1 [file] [log] [blame]
Matteo Scandoloeeb9c082016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
4 angular.module('xos.serviceTopology')
Matteo Scandolo38ba3312016-02-09 16:01:49 -08005 .service('LogicTopologyHelper', function($window, $log, lodash, serviceTopologyConfig, NodeDrawer){
Matteo Scandolo11dc8c42016-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 Scandolo7fd4d042016-02-16 14:44:51 -080020 computeNodes: [],
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080021 children: [
22 {
23 name: 'LAN',
24 type: 'network',
25 children: [] //subscribers goes here
26 }
27 ]
28 }
29 ]
30 }
31 ]
32 };
33
Matteo Scandolo38ba3312016-02-09 16:01:49 -080034 /**
Matteo Scandolo594dfbc2016-02-11 17:37:08 -080035 * Calculate the horizontal position for each element.
36 * subsrcribers, devices and routers have the same fixed width 20
37 * network have a fixed width 104
38 * rack have a fixed width 105
39 * build and array of 6 elements representing the position of each element in the svg
40 * to equally space them
41 */
42
43 this.computeElementPosition = (svgWidth) => {
44
45 let xPos = [];
46
47 let totalElWidth = lodash.reduce(serviceTopologyConfig.elWidths, (el, val) => val + el, 0);
48
Matteo Scandolofc532ed2016-02-11 17:58:18 -080049 let remainingSpace = svgWidth - totalElWidth - (serviceTopologyConfig.widthMargin * 2);
Matteo Scandolo594dfbc2016-02-11 17:37:08 -080050
51 let step = remainingSpace / (serviceTopologyConfig.elWidths.length - 1);
52
53 lodash.forEach(serviceTopologyConfig.elWidths, (el, i) => {
54
55 // get half of the previous elements width
56 let previousElWidth = 0;
57 if(i !== 0){
Matteo Scandolofc532ed2016-02-11 17:58:18 -080058 previousElWidth = lodash.reduce(serviceTopologyConfig.elWidths.slice(0, i), (el, val) => val + el, 0);
Matteo Scandolo594dfbc2016-02-11 17:37:08 -080059 }
60
61 let elPos =
62 serviceTopologyConfig.widthMargin // right margin
63 + (step * i) // space between elements
64 + (el / 2) // this el width
65 + previousElWidth; // previous elements width
66
67 xPos.push(svgWidth - elPos);
68 })
69
70 return xPos
71 };
72
73 /**
Matteo Scandolo38ba3312016-02-09 16:01:49 -080074 * from a nested data structure,
75 * create nodes and links for a D3 Tree Layout
76 */
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080077 const computeLayout = (data) => {
78 let nodes = layout.nodes(data);
79
80 // Normalize for fixed-depth.
Matteo Scandolo594dfbc2016-02-11 17:37:08 -080081 nodes.forEach((d) => {
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080082 // position the child node horizontally
Matteo Scandolo594dfbc2016-02-11 17:37:08 -080083 d.y = this.computeElementPosition(svgWidth)[d.depth];
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080084 });
85
86 let links = layout.links(nodes);
Matteo Scandolo38ba3312016-02-09 16:01:49 -080087
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080088 return [nodes, links];
89 };
90
Matteo Scandolo38ba3312016-02-09 16:01:49 -080091 /**
92 * Draw the containing group for any node or update the existing one
93 */
Matteo Scandolo11dc8c42016-02-09 14:46:14 -080094 const drawNodes = (svg, nodes) => {
95 // Update the nodes…
96 var node = svg.selectAll('g.node')
Matteo Scandolo38ba3312016-02-09 16:01:49 -080097 .data(nodes, d => {
98 if(!angular.isString(d.d3Id)){
99 d.d3Id = `tree-${++i}`;
100 }
101 return d.d3Id;
102 });
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800103
Matteo Scandolo38ba3312016-02-09 16:01:49 -0800104 // Enter any new nodes
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800105 var nodeEnter = node.enter().append('g')
106 .attr({
107 class: d => `node ${d.type}`,
108 transform: `translate(${svgWidth / 2}, ${svgHeight / 2})`
109 });
110
Matteo Scandoloc2c6fb02016-02-16 16:03:43 -0800111 // create Nodes
Matteo Scandoloedd3d6f2016-02-16 15:06:11 -0800112 NodeDrawer.addNetworks(node.filter('.network'));
113 NodeDrawer.addRack(node.filter('.rack'));
114 NodeDrawer.addPhisical(node.filter('.router'));
115 NodeDrawer.addPhisical(node.filter('.subscriber'));
116 NodeDrawer.addDevice(node.filter('.device'));
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800117
Matteo Scandoloc2c6fb02016-02-16 16:03:43 -0800118 //update nodes
119 // TODO if data change, only update them
120 // NodeDrawer.updateRack(node.filter('.rack'));
121
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800122 // Transition nodes to their new position.
123 var nodeUpdate = node.transition()
124 .duration(serviceTopologyConfig.duration)
125 .attr({
126 'transform': d => `translate(${d.y},${d.x})`
127 });
128
129 // TODO handle node remove
130 };
131
Matteo Scandolo38ba3312016-02-09 16:01:49 -0800132 /**
133 * Handle links in the tree layout
134 */
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800135 const drawLinks = (svg, links) => {
136
137 diagonal = d3.svg.diagonal()
138 .projection(d => [d.y, d.x]);
139
140 // Update the links…
141 var link = svg.selectAll('path.link')
142 .data(links, d => {
Matteo Scandolo38ba3312016-02-09 16:01:49 -0800143 return d.target.d3Id
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800144 });
145
146 // Enter any new links at the parent's previous position.
147 link.enter().insert('path', 'g')
148 .attr('class', d => `link ${d.target.type}`)
149 .attr('d', function(d) {
150 var o = {x: svgHeight / 2, y: svgWidth / 2};
151 return diagonal({source: o, target: o});
152 });
153
154 // Transition links to their new position.
155 link.transition()
156 .duration(serviceTopologyConfig.duration)
157 .attr('d', diagonal);
158 };
159
Matteo Scandolo38ba3312016-02-09 16:01:49 -0800160 /**
161 * Calculate the svg size and setup tree layout
162 */
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800163 this.setupTree = (svg) => {
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800164
165
166 svgWidth = svg.node().getBoundingClientRect().width;
167 svgHeight = svg.node().getBoundingClientRect().height;
168
169 const width = svgWidth - (serviceTopologyConfig.widthMargin * 2);
170 const height = svgHeight - (serviceTopologyConfig.heightMargin * 2);
171
172 layout = d3.layout.tree()
173 .size([height, width]);
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800174 };
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800175
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800176 /**
177 * Update the tree layout
178 */
179
180 this.updateTree = (svg) => {
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800181 // Compute the new tree layout.
182 [nodes, links] = computeLayout(baseData);
183
184 drawNodes(svg, nodes);
185 drawLinks(svg, links);
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800186 }
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800187
Matteo Scandolo38ba3312016-02-09 16:01:49 -0800188 /**
189 * Add Subscribers to the tree
190 */
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800191 this.addSubscribers = (subscribers) => {
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800192
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800193 subscribers.map((subscriber) => {
194 subscriber.children = subscriber.devices;
195 });
196
197 // add subscriber to data tree
198 baseData.children[0].children[0].children[0].children = subscribers;
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800199
200 return baseData;
201 };
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800202
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800203 /**
204 * Add compute nodes to the rack element
205 */
206
207 this.addComputeNodes = (computeNodes) => {
208 baseData.children[0].children[0].computeNodes = computeNodes;
Matteo Scandoloedd3d6f2016-02-16 15:06:11 -0800209 };
210
211 this.getInstanceStatus = (instanceId) => {
212
213 const computeNodes = baseData.children[0].children[0].computeNodes;
214
Matteo Scandoloc2c6fb02016-02-16 16:03:43 -0800215 // unselect all
216 computeNodes.map((node) => {
217 node.instances.map((instance) => {
218 instance.selected = false
219 return instance;
220 });
221 });
222
Matteo Scandoloedd3d6f2016-02-16 15:06:11 -0800223 let targetInstance = computeNodes.reduce((selected, node) => {
224 let found = lodash.find(node.instances, {id: instanceId});
225
226 if(found){
227 return found;
228 }
229 }, null);
230
231 // object are passed by reference,
232 // updating this update the instance in the tree
Matteo Scandoloc2c6fb02016-02-16 16:03:43 -0800233 if(targetInstance){
234 targetInstance.selected = true;
235 }
Matteo Scandoloedd3d6f2016-02-16 15:06:11 -0800236
Matteo Scandolo11dc8c42016-02-09 14:46:14 -0800237 }
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800238 });
239
240}());