blob: 04df8fca43f4c6dfe5f11052ecac40ee3c6c2fa4 [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
94 // given a canvas, a layout and a data source, draw a tree layout
95 const updateTree = (svg, layout, source) => {
96
97 //cache data
98 _svg = svg;
99 _layout = layout;
100 _source = source;
101
102 const maxDepth = ServiceRelation.depthOf(source);
103
104 const diagonal = d3.svg.diagonal()
105 .projection(d => [d.y, d.x]);
106
107 // Compute the new tree layout.
108 var nodes = layout.nodes(source).reverse(),
109 links = layout.links(nodes);
110
111 // Normalize for fixed-depth.
112 nodes.forEach(function(d) {
113 // position the child node horizontally
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800114 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
115 d.y = d.depth * step;
116 if(d.type === 'slice' || d.type === 'instance'){
117 d.y = d.depth * step - (step / 2);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800118 }
119 });
120
121 // Update the nodes…
122 var node = svg.selectAll('g.node')
123 .data(nodes, function(d) { return d.id || (d.id = ++i); });
124
125 // Enter any new nodes at the parent's previous position.
126 var nodeEnter = node.enter().append('g')
127 .attr({
128 class: d => `node ${d.type}`,
129 transform: d => `translate(${source.y0},${source.x0})` // this is the starting position
130 });
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800131
132 const subscriberNodes = nodeEnter.filter('.subscriber');
133 const internetNodes = nodeEnter.filter('.internet');
134 const serviceNodes = nodeEnter.filter('.service');
135 const instanceNodes = nodeEnter.filter('.instance');
136 const sliceNodes = nodeEnter.filter('.slice');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800137
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800138 subscriberNodes.append('path')
139 .attr({
140 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',
141 transform: 'translate(-20, -20)'
142 });
143
144 internetNodes.append('path')
145 .attr({
146 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',
147 stroke: '#000',
148 'stroke-width': '20px',
149 fill: 'none',
150 transform: 'translate(-10, -10), scale(0.05)'
151 });
152
153 serviceNodes.append('circle')
Matteo Scandolo071ef462016-01-25 12:00:42 -0800154 .attr('r', 1e-6)
155 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
156 .on('click', serviceClick);
157
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800158 sliceNodes.append('rect')
159 .attr({
160 width: 20,
161 height: 20,
162 x: -10,
163 y: -10
164 });
165
166 instanceNodes.append('rect')
167 .attr({
168 width: 20,
169 height: 20,
170 x: -10,
171 y: -10
172 });
173
Matteo Scandolo071ef462016-01-25 12:00:42 -0800174 nodeEnter.append('text')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800175 .attr({
176 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
177 dy: '.35em',
178 transform: d => {
179 if (d.children && d.parent){
180 if(d.parent.x < d.x){
181 return 'rotate(-30)';
182 }
183 return 'rotate(30)';
184 }
185 },
186 'text-anchor': d => d.children ? 'end' : 'start'
Matteo Scandolo071ef462016-01-25 12:00:42 -0800187 })
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800188 .text(d => d.name)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800189 .style('fill-opacity', 1e-6);
190
191 // Transition nodes to their new position.
192 var nodeUpdate = node.transition()
193 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800194 .attr({
195 'transform': d => `translate(${d.y},${d.x})`
Matteo Scandolo071ef462016-01-25 12:00:42 -0800196 });
197
198 nodeUpdate.select('circle')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800199 .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
200 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800201
202 nodeUpdate.select('text')
203 .style('fill-opacity', 1);
204
205 // Transition exiting nodes to the parent's new position.
206 var nodeExit = node.exit().transition()
207 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800208 .attr({
209 'transform': d => `translate(${source.y},${source.x})`
210 })
Matteo Scandolo071ef462016-01-25 12:00:42 -0800211 .remove();
212
213 nodeExit.select('circle')
214 .attr('r', 1e-6);
215
216 nodeExit.select('text')
217 .style('fill-opacity', 1e-6);
218
219 // Update the links…
220 var link = svg.selectAll('path.link')
221 .data(links, function(d) { return d.target.id; });
222
223 // Enter any new links at the parent's previous position.
224 link.enter().insert('path', 'g')
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800225 .attr('class', d => `link ${d.target.type}`)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800226 .attr('d', function(d) {
227 var o = {x: source.x0, y: source.y0};
228 return diagonal({source: o, target: o});
229 });
230
231 // Transition links to their new position.
232 link.transition()
233 .duration(serviceTopologyConfig.duration)
234 .attr('d', diagonal);
235
236 // Transition exiting nodes to the parent's new position.
237 link.exit().transition()
238 .duration(serviceTopologyConfig.duration)
239 .attr('d', function(d) {
240 var o = {x: source.x, y: source.y};
241 return diagonal({source: o, target: o});
242 })
243 .remove();
244
245 // Stash the old positions for transition.
246 nodes.forEach(function(d) {
247 d.x0 = d.x;
248 d.y0 = d.y;
249 });
250 };
251
252 const serviceClick = function(d) {
253
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800254 if(!d.service){
255 return;
256 }
257
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800258 // toggling selected status
259 d.selected = !d.selected;
Matteo Scandolo071ef462016-01-25 12:00:42 -0800260
Matteo Scandolo071ef462016-01-25 12:00:42 -0800261 var selectedNode = d3.select(this);
262
263 selectedNode
264 .transition()
265 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800266 .attr('r', serviceTopologyConfig.circle.selectedRadius);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800267
Matteo Scandolo071ef462016-01-25 12:00:42 -0800268 ServiceRelation.getServiceInterfaces(d.service.id)
269 .then(interfaceTree => {
270
271 const isDetailed = lodash.find(d.children, {type: 'slice'});
272 if(isDetailed){
Matteo Scandolo071ef462016-01-25 12:00:42 -0800273 lodash.remove(d.children, {type: 'slice'});
274 }
275 else {
Matteo Scandolo071ef462016-01-25 12:00:42 -0800276 d.children = d.children.concat(interfaceTree);
277 }
278
279 updateTree(_svg, _layout, _source);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800280 });
281 };
282
283 this.updateTree = updateTree;
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -0800284 this.drawLegend = drawLegend;
Matteo Scandolof2c99012016-01-25 10:10:38 -0800285 });
Matteo Scandolo8a64fa42016-01-21 11:21:03 -0800286
287}());