blob: 46723259caa44e3f1d4d35313e2a95afd91054cd [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,
Matteo Scandoloc9ebd922016-01-28 12:02:57 -0800184 y: -10,
185 class: d => d.active ?'' : 'active'
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800186 });
187
Matteo Scandolo071ef462016-01-25 12:00:42 -0800188 nodeEnter.append('text')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800189 .attr({
190 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
191 dy: '.35em',
192 transform: d => {
193 if (d.children && d.parent){
194 if(d.parent.x < d.x){
195 return 'rotate(-30)';
196 }
197 return 'rotate(30)';
198 }
199 },
200 'text-anchor': d => d.children ? 'end' : 'start'
Matteo Scandolo071ef462016-01-25 12:00:42 -0800201 })
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800202 .text(d => d.name)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800203 .style('fill-opacity', 1e-6);
204
205 // Transition nodes to their new position.
206 var nodeUpdate = node.transition()
207 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800208 .attr({
209 'transform': d => `translate(${d.y},${d.x})`
Matteo Scandolo071ef462016-01-25 12:00:42 -0800210 });
211
212 nodeUpdate.select('circle')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800213 .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
214 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800215
216 nodeUpdate.select('text')
217 .style('fill-opacity', 1);
218
219 // Transition exiting nodes to the parent's new position.
220 var nodeExit = node.exit().transition()
221 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800222 .attr({
Matteo Scandolo21c14612016-01-25 17:46:37 -0800223 'transform': d => getInitialNodePosition(d, source, 'exit')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800224 })
Matteo Scandolo071ef462016-01-25 12:00:42 -0800225 .remove();
226
227 nodeExit.select('circle')
228 .attr('r', 1e-6);
229
230 nodeExit.select('text')
231 .style('fill-opacity', 1e-6);
232
233 // Update the links…
234 var link = svg.selectAll('path.link')
235 .data(links, function(d) { return d.target.id; });
236
237 // Enter any new links at the parent's previous position.
238 link.enter().insert('path', 'g')
Matteo Scandoloc9ebd922016-01-28 12:02:57 -0800239 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800240 .attr('d', function(d) {
241 var o = {x: source.x0, y: source.y0};
242 return diagonal({source: o, target: o});
243 });
244
245 // Transition links to their new position.
246 link.transition()
247 .duration(serviceTopologyConfig.duration)
248 .attr('d', diagonal);
249
250 // Transition exiting nodes to the parent's new position.
251 link.exit().transition()
252 .duration(serviceTopologyConfig.duration)
253 .attr('d', function(d) {
254 var o = {x: source.x, y: source.y};
255 return diagonal({source: o, target: o});
256 })
257 .remove();
258
259 // Stash the old positions for transition.
260 nodes.forEach(function(d) {
261 d.x0 = d.x;
262 d.y0 = d.y;
263 });
264 };
265
266 const serviceClick = function(d) {
267
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800268 if(!d.service){
269 return;
270 }
271
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800272 // toggling selected status
273 d.selected = !d.selected;
Matteo Scandolo071ef462016-01-25 12:00:42 -0800274
Matteo Scandolo071ef462016-01-25 12:00:42 -0800275 var selectedNode = d3.select(this);
276
277 selectedNode
278 .transition()
279 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800280 .attr('r', serviceTopologyConfig.circle.selectedRadius);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800281
Matteo Scandoloc9ebd922016-01-28 12:02:57 -0800282 ServiceRelation.getServiceInterfaces(d.service)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800283 .then(interfaceTree => {
284
285 const isDetailed = lodash.find(d.children, {type: 'slice'});
286 if(isDetailed){
Matteo Scandolo071ef462016-01-25 12:00:42 -0800287 lodash.remove(d.children, {type: 'slice'});
288 }
289 else {
Matteo Scandolo071ef462016-01-25 12:00:42 -0800290 d.children = d.children.concat(interfaceTree);
291 }
292
293 updateTree(_svg, _layout, _source);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800294 });
295 };
296
297 this.updateTree = updateTree;
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -0800298 this.drawLegend = drawLegend;
Matteo Scandolof2c99012016-01-25 10:10:38 -0800299 });
Matteo Scandolo8a64fa42016-01-21 11:21:03 -0800300
301}());