blob: 59c3b5f1fa13c1e1cb67ad9a44226a64ca4e7d98 [file] [log] [blame]
Matteo Scandolo8a64fa42016-01-21 11:21:03 -08001(function () {
2 'use strict';
3
4 angular.module('xos.serviceTopology')
5 .factory('d3', function($window){
6 return $window.d3;
7 })
Matteo Scandolo071ef462016-01-25 12:00:42 -08008 .service('TreeLayout', function($window, lodash, ServiceRelation, serviceTopologyConfig, Slice, Instances){
Matteo Scandolof2c99012016-01-25 10:10:38 -08009
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080010 const drawLegend = (svg) => {
11 const legendContainer = svg.append('g')
12 .attr({
13 class: 'legend'
14 });
15
16 legendContainer.append('rect')
17 .attr({
18 transform: d => `translate(10, 80)`,
19 width: 100,
Matteo Scandolo5bf04572016-01-25 17:36:08 -080020 height: 100
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080021 });
22
23 // service
24 const service = legendContainer.append('g')
25 .attr({
26 class: 'node service'
27 });
28
29 service.append('circle')
30 .attr({
31 r: serviceTopologyConfig.circle.radius,
32 transform: d => `translate(30, 100)`
33 });
34
35 service.append('text')
36 .attr({
37 transform: d => `translate(45, 100)`,
38 dy: '.35em'
39 })
40 .text('Service')
41 .style('fill-opacity', 1);
42
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080043 // slice
44 const slice = legendContainer.append('g')
45 .attr({
46 class: 'node slice'
47 });
48
Matteo Scandolo5bf04572016-01-25 17:36:08 -080049 slice.append('rect')
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080050 .attr({
Matteo Scandolo5bf04572016-01-25 17:36:08 -080051 width: 20,
52 height: 20,
53 x: -10,
54 y: -10,
55 transform: d => `translate(30, 130)`
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080056 });
57
58 slice.append('text')
59 .attr({
Matteo Scandolo5bf04572016-01-25 17:36:08 -080060 transform: d => `translate(45, 130)`,
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080061 dy: '.35em'
62 })
63 .text('Slices')
64 .style('fill-opacity', 1);
65
66 // instance
67 const instance = legendContainer.append('g')
68 .attr({
69 class: 'node instance'
70 });
71
Matteo Scandolo5bf04572016-01-25 17:36:08 -080072 instance.append('rect')
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080073 .attr({
Matteo Scandolo5bf04572016-01-25 17:36:08 -080074 width: 20,
75 height: 20,
76 x: -10,
77 y: -10,
78 transform: d => `translate(30, 160)`
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080079 });
80
81 instance.append('text')
82 .attr({
Matteo Scandolo5bf04572016-01-25 17:36:08 -080083 transform: d => `translate(45, 160)`,
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -080084 dy: '.35em'
85 })
86 .text('Instances')
87 .style('fill-opacity', 1);
88 };
89
Matteo Scandolo071ef462016-01-25 12:00:42 -080090 var _svg, _layout, _source;
91
92 var i = 0;
93
Matteo Scandolo21c14612016-01-25 17:46:37 -080094 const getInitialNodePosition = (node, source, direction = 'enter') => {
95 // this is the starting position
96 // TODO if enter use y0 and x0 else x and y
97 // TODO start from the node that has been clicked
98 if(node.parent){
99 if(direction === 'enter' && node.parent.y0 && node.parent.x0){
100 return `translate(${node.parent.y0},${node.parent.x0})`;
101 }
102 return `translate(${node.parent.y},${node.parent.x})`;
103 }
104 return `translate(${source.y0},${source.x0})`;
105 };
106
Matteo Scandolo071ef462016-01-25 12:00:42 -0800107 // given a canvas, a layout and a data source, draw a tree layout
108 const updateTree = (svg, layout, source) => {
109
110 //cache data
111 _svg = svg;
112 _layout = layout;
113 _source = source;
114
115 const maxDepth = ServiceRelation.depthOf(source);
116
117 const diagonal = d3.svg.diagonal()
118 .projection(d => [d.y, d.x]);
119
120 // Compute the new tree layout.
121 var nodes = layout.nodes(source).reverse(),
122 links = layout.links(nodes);
123
124 // Normalize for fixed-depth.
125 nodes.forEach(function(d) {
126 // position the child node horizontally
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800127 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
128 d.y = d.depth * step;
129 if(d.type === 'slice' || d.type === 'instance'){
130 d.y = d.depth * step - (step / 2);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800131 }
132 });
133
134 // Update the nodes…
135 var node = svg.selectAll('g.node')
136 .data(nodes, function(d) { return d.id || (d.id = ++i); });
137
138 // Enter any new nodes at the parent's previous position.
139 var nodeEnter = node.enter().append('g')
140 .attr({
141 class: d => `node ${d.type}`,
Matteo Scandolo21c14612016-01-25 17:46:37 -0800142 transform: d => getInitialNodePosition(d, source)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800143 });
Matteo Scandolo21c14612016-01-25 17:46:37 -0800144
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800145 const subscriberNodes = nodeEnter.filter('.subscriber');
146 const internetNodes = nodeEnter.filter('.internet');
147 const serviceNodes = nodeEnter.filter('.service');
148 const instanceNodes = nodeEnter.filter('.instance');
149 const sliceNodes = nodeEnter.filter('.slice');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800150
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800151 subscriberNodes.append('path')
152 .attr({
153 d: 'M20.771,12.364c0,0,0.849-3.51,0-4.699c-0.85-1.189-1.189-1.981-3.058-2.548s-1.188-0.454-2.547-0.396c-1.359,0.057-2.492,0.792-2.492,1.188c0,0-0.849,0.057-1.188,0.397c-0.34,0.34-0.906,1.924-0.906,2.321s0.283,3.058,0.566,3.624l-0.337,0.113c-0.283,3.283,1.132,3.68,1.132,3.68c0.509,3.058,1.019,1.756,1.019,2.548s-0.51,0.51-0.51,0.51s-0.452,1.245-1.584,1.698c-1.132,0.452-7.416,2.886-7.927,3.396c-0.511,0.511-0.453,2.888-0.453,2.888h26.947c0,0,0.059-2.377-0.452-2.888c-0.512-0.511-6.796-2.944-7.928-3.396c-1.132-0.453-1.584-1.698-1.584-1.698s-0.51,0.282-0.51-0.51s0.51,0.51,1.02-2.548c0,0,1.414-0.397,1.132-3.68H20.771z',
154 transform: 'translate(-20, -20)'
155 });
156
157 internetNodes.append('path')
158 .attr({
159 d: 'M209,15a195,195 0 1,0 2,0zm1,0v390m195-195H15M59,90a260,260 0 0,0 302,0 m0,240 a260,260 0 0,0-302,0M195,20a250,250 0 0,0 0,382 m30,0 a250,250 0 0,0 0-382',
160 stroke: '#000',
161 'stroke-width': '20px',
162 fill: 'none',
163 transform: 'translate(-10, -10), scale(0.05)'
164 });
165
166 serviceNodes.append('circle')
Matteo Scandolo071ef462016-01-25 12:00:42 -0800167 .attr('r', 1e-6)
168 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
169 .on('click', serviceClick);
170
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800171 sliceNodes.append('rect')
172 .attr({
173 width: 20,
174 height: 20,
175 x: -10,
176 y: -10
177 });
178
179 instanceNodes.append('rect')
180 .attr({
181 width: 20,
182 height: 20,
183 x: -10,
184 y: -10
185 });
186
Matteo Scandolo071ef462016-01-25 12:00:42 -0800187 nodeEnter.append('text')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800188 .attr({
189 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
190 dy: '.35em',
191 transform: d => {
192 if (d.children && d.parent){
193 if(d.parent.x < d.x){
194 return 'rotate(-30)';
195 }
196 return 'rotate(30)';
197 }
198 },
199 'text-anchor': d => d.children ? 'end' : 'start'
Matteo Scandolo071ef462016-01-25 12:00:42 -0800200 })
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800201 .text(d => d.name)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800202 .style('fill-opacity', 1e-6);
203
204 // Transition nodes to their new position.
205 var nodeUpdate = node.transition()
206 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800207 .attr({
208 'transform': d => `translate(${d.y},${d.x})`
Matteo Scandolo071ef462016-01-25 12:00:42 -0800209 });
210
211 nodeUpdate.select('circle')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800212 .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
213 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800214
215 nodeUpdate.select('text')
216 .style('fill-opacity', 1);
217
218 // Transition exiting nodes to the parent's new position.
219 var nodeExit = node.exit().transition()
220 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800221 .attr({
Matteo Scandolo21c14612016-01-25 17:46:37 -0800222 'transform': d => getInitialNodePosition(d, source, 'exit')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800223 })
Matteo Scandolo071ef462016-01-25 12:00:42 -0800224 .remove();
225
226 nodeExit.select('circle')
227 .attr('r', 1e-6);
228
229 nodeExit.select('text')
230 .style('fill-opacity', 1e-6);
231
232 // Update the links…
233 var link = svg.selectAll('path.link')
234 .data(links, function(d) { return d.target.id; });
235
236 // Enter any new links at the parent's previous position.
237 link.enter().insert('path', 'g')
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800238 .attr('class', d => `link ${d.target.type}`)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800239 .attr('d', function(d) {
240 var o = {x: source.x0, y: source.y0};
241 return diagonal({source: o, target: o});
242 });
243
244 // Transition links to their new position.
245 link.transition()
246 .duration(serviceTopologyConfig.duration)
247 .attr('d', diagonal);
248
249 // Transition exiting nodes to the parent's new position.
250 link.exit().transition()
251 .duration(serviceTopologyConfig.duration)
252 .attr('d', function(d) {
253 var o = {x: source.x, y: source.y};
254 return diagonal({source: o, target: o});
255 })
256 .remove();
257
258 // Stash the old positions for transition.
259 nodes.forEach(function(d) {
260 d.x0 = d.x;
261 d.y0 = d.y;
262 });
263 };
264
265 const serviceClick = function(d) {
266
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800267 if(!d.service){
268 return;
269 }
270
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800271 // toggling selected status
272 d.selected = !d.selected;
Matteo Scandolo071ef462016-01-25 12:00:42 -0800273
Matteo Scandolo071ef462016-01-25 12:00:42 -0800274 var selectedNode = d3.select(this);
275
276 selectedNode
277 .transition()
278 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800279 .attr('r', serviceTopologyConfig.circle.selectedRadius);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800280
Matteo Scandolo071ef462016-01-25 12:00:42 -0800281 ServiceRelation.getServiceInterfaces(d.service.id)
282 .then(interfaceTree => {
283
284 const isDetailed = lodash.find(d.children, {type: 'slice'});
285 if(isDetailed){
Matteo Scandolo071ef462016-01-25 12:00:42 -0800286 lodash.remove(d.children, {type: 'slice'});
287 }
288 else {
Matteo Scandolo071ef462016-01-25 12:00:42 -0800289 d.children = d.children.concat(interfaceTree);
290 }
291
292 updateTree(_svg, _layout, _source);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800293 });
294 };
295
296 this.updateTree = updateTree;
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -0800297 this.drawLegend = drawLegend;
Matteo Scandolof2c99012016-01-25 10:10:38 -0800298 });
Matteo Scandolo8a64fa42016-01-21 11:21:03 -0800299
300}());