blob: a05fcffb3914bd0731fbca77affa2d5a2ac01a30 [file] [log] [blame]
Matteo Scandolo219b1a72016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
4 angular.module('xos.serviceTopology')
Matteo Scandolo9fe01af2016-02-09 16:01:49 -08005 .service('LogicTopologyHelper', function($window, $log, 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',
20 children: [
21 {
22 name: 'LAN',
23 type: 'network',
24 children: [] //subscribers goes here
25 }
26 ]
27 }
28 ]
29 }
30 ]
31 };
32
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080033 /**
Matteo Scandolocc0db942016-02-11 17:37:08 -080034 * Calculate the horizontal position for each element.
35 * subsrcribers, devices and routers have the same fixed width 20
36 * network have a fixed width 104
37 * rack have a fixed width 105
38 * build and array of 6 elements representing the position of each element in the svg
39 * to equally space them
40 */
41
42 this.computeElementPosition = (svgWidth) => {
43
44 let xPos = [];
45
46 let totalElWidth = lodash.reduce(serviceTopologyConfig.elWidths, (el, val) => val + el, 0);
47
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080048 let remainingSpace = svgWidth - totalElWidth - (serviceTopologyConfig.widthMargin * 2);
Matteo Scandolocc0db942016-02-11 17:37:08 -080049
50 let step = remainingSpace / (serviceTopologyConfig.elWidths.length - 1);
51
52 lodash.forEach(serviceTopologyConfig.elWidths, (el, i) => {
53
54 // get half of the previous elements width
55 let previousElWidth = 0;
56 if(i !== 0){
Matteo Scandolobec0a6c2016-02-11 17:58:18 -080057 previousElWidth = lodash.reduce(serviceTopologyConfig.elWidths.slice(0, i), (el, val) => val + el, 0);
Matteo Scandolocc0db942016-02-11 17:37:08 -080058 }
59
60 let elPos =
61 serviceTopologyConfig.widthMargin // right margin
62 + (step * i) // space between elements
63 + (el / 2) // this el width
64 + previousElWidth; // previous elements width
65
66 xPos.push(svgWidth - elPos);
67 })
68
69 return xPos
70 };
71
72 /**
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080073 * from a nested data structure,
74 * create nodes and links for a D3 Tree Layout
75 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080076 const computeLayout = (data) => {
77 let nodes = layout.nodes(data);
78
79 // Normalize for fixed-depth.
Matteo Scandolocc0db942016-02-11 17:37:08 -080080 nodes.forEach((d) => {
Matteo Scandoloaf286372016-02-09 14:46:14 -080081 // position the child node horizontally
Matteo Scandolocc0db942016-02-11 17:37:08 -080082 // const step = ((svgWidth - (serviceTopologyConfig.widthMargin * 2)) / 7);
83 // d.y = (6 - d.depth) * step;
84 d.y = this.computeElementPosition(svgWidth)[d.depth];
85 console.log(d.id, d.y);
Matteo Scandoloaf286372016-02-09 14:46:14 -080086 });
87
88 let links = layout.links(nodes);
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080089
Matteo Scandoloaf286372016-02-09 14:46:14 -080090 return [nodes, links];
91 };
92
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080093 /**
94 * Draw the containing group for any node or update the existing one
95 */
Matteo Scandoloaf286372016-02-09 14:46:14 -080096 const drawNodes = (svg, nodes) => {
97 // Update the nodes…
98 var node = svg.selectAll('g.node')
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080099 .data(nodes, d => {
100 if(!angular.isString(d.d3Id)){
101 d.d3Id = `tree-${++i}`;
102 }
103 return d.d3Id;
104 });
Matteo Scandoloaf286372016-02-09 14:46:14 -0800105
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800106 // Enter any new nodes
Matteo Scandoloaf286372016-02-09 14:46:14 -0800107 var nodeEnter = node.enter().append('g')
108 .attr({
109 class: d => `node ${d.type}`,
110 transform: `translate(${svgWidth / 2}, ${svgHeight / 2})`
111 });
112
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800113 NodeDrawer.addNetworks(nodeEnter.filter('.network'));
114 NodeDrawer.addRack(nodeEnter.filter('.rack'));
115 NodeDrawer.addPhisical(nodeEnter.filter('.router'));
116 NodeDrawer.addPhisical(nodeEnter.filter('.subscriber'));
117 NodeDrawer.addDevice(nodeEnter.filter('.device'));
Matteo Scandoloaf286372016-02-09 14:46:14 -0800118
119 // Transition nodes to their new position.
120 var nodeUpdate = node.transition()
121 .duration(serviceTopologyConfig.duration)
122 .attr({
123 'transform': d => `translate(${d.y},${d.x})`
124 });
125
126 // TODO handle node remove
127 };
128
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800129 /**
130 * Handle links in the tree layout
131 */
Matteo Scandoloaf286372016-02-09 14:46:14 -0800132 const drawLinks = (svg, links) => {
133
134 diagonal = d3.svg.diagonal()
135 .projection(d => [d.y, d.x]);
136
137 // Update the links…
138 var link = svg.selectAll('path.link')
139 .data(links, d => {
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800140 return d.target.d3Id
Matteo Scandoloaf286372016-02-09 14:46:14 -0800141 });
142
143 // Enter any new links at the parent's previous position.
144 link.enter().insert('path', 'g')
145 .attr('class', d => `link ${d.target.type}`)
146 .attr('d', function(d) {
147 var o = {x: svgHeight / 2, y: svgWidth / 2};
148 return diagonal({source: o, target: o});
149 });
150
151 // Transition links to their new position.
152 link.transition()
153 .duration(serviceTopologyConfig.duration)
154 .attr('d', diagonal);
155 };
156
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800157 /**
158 * Calculate the svg size and setup tree layout
159 */
Matteo Scandoloaf286372016-02-09 14:46:14 -0800160 this.drawTree = (svg) => {
161
162
163 svgWidth = svg.node().getBoundingClientRect().width;
164 svgHeight = svg.node().getBoundingClientRect().height;
165
166 const width = svgWidth - (serviceTopologyConfig.widthMargin * 2);
167 const height = svgHeight - (serviceTopologyConfig.heightMargin * 2);
168
169 layout = d3.layout.tree()
170 .size([height, width]);
171
172 // Compute the new tree layout.
173 [nodes, links] = computeLayout(baseData);
174
175 drawNodes(svg, nodes);
176 drawLinks(svg, links);
177
178 };
179
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800180 /**
181 * Add Subscribers to the tree
182 */
Matteo Scandoloaf286372016-02-09 14:46:14 -0800183 this.addSubscribers = (svg, subscribers) => {
184
Matteo Scandoloaf286372016-02-09 14:46:14 -0800185 subscribers.map((subscriber) => {
186 subscriber.children = subscriber.devices;
187 });
188
189 // add subscriber to data tree
190 baseData.children[0].children[0].children[0].children = subscribers;
191
Matteo Scandoloaf286372016-02-09 14:46:14 -0800192 [nodes, links] = computeLayout(baseData);
193
194 drawNodes(svg, nodes);
195 drawLinks(svg, links);
196 }
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800197 });
198
199}());