blob: c8649aaec7719360eaf0ffc1c19d23c002e56b6d [file] [log] [blame]
Matteo Scandolobe9b13d2016-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 Scandolo4889f5a2016-01-25 12:00:42 -08008 .service('TreeLayout', function($window, lodash, ServiceRelation, serviceTopologyConfig, Slice, Instances){
Matteo Scandolofb46f5b2016-01-25 10:10:38 -08009
Matteo Scandolo65d6fc42016-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,
20 height: 130
21 });
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
43 // endpoints
44 const endpoints = legendContainer.append('g')
45 .attr({
46 class: 'node internet'
47 });
48
49 endpoints.append('circle')
50 .attr({
51 r: serviceTopologyConfig.circle.radius,
52 transform: d => `translate(30, 130)`
53 });
54
55 endpoints.append('text')
56 .attr({
57 transform: d => `translate(45, 130)`,
58 dy: '.35em'
59 })
60 .text('Enpoints')
61 .style('fill-opacity', 1);
62
63 // slice
64 const slice = legendContainer.append('g')
65 .attr({
66 class: 'node slice'
67 });
68
69 slice.append('circle')
70 .attr({
71 r: serviceTopologyConfig.circle.radius,
72 transform: d => `translate(30, 160)`
73 });
74
75 slice.append('text')
76 .attr({
77 transform: d => `translate(45, 160)`,
78 dy: '.35em'
79 })
80 .text('Slices')
81 .style('fill-opacity', 1);
82
83 // instance
84 const instance = legendContainer.append('g')
85 .attr({
86 class: 'node instance'
87 });
88
89 instance.append('circle')
90 .attr({
91 r: serviceTopologyConfig.circle.radius,
92 transform: d => `translate(30, 190)`
93 });
94
95 instance.append('text')
96 .attr({
97 transform: d => `translate(45, 190)`,
98 dy: '.35em'
99 })
100 .text('Instances')
101 .style('fill-opacity', 1);
102 };
103
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800104 var _svg, _layout, _source;
105
106 var i = 0;
107
108 // given a canvas, a layout and a data source, draw a tree layout
109 const updateTree = (svg, layout, source) => {
110
111 //cache data
112 _svg = svg;
113 _layout = layout;
114 _source = source;
115
116 const maxDepth = ServiceRelation.depthOf(source);
117
118 const diagonal = d3.svg.diagonal()
119 .projection(d => [d.y, d.x]);
120
121 // Compute the new tree layout.
122 var nodes = layout.nodes(source).reverse(),
123 links = layout.links(nodes);
124
125 // Normalize for fixed-depth.
126 nodes.forEach(function(d) {
127 // position the child node horizontally
Matteo Scandolo39f49472016-01-25 13:55:09 -0800128 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
129 d.y = d.depth * step;
130 if(d.type === 'slice' || d.type === 'instance'){
131 d.y = d.depth * step - (step / 2);
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800132 }
133 });
134
135 // Update the nodes…
136 var node = svg.selectAll('g.node')
137 .data(nodes, function(d) { return d.id || (d.id = ++i); });
138
139 // Enter any new nodes at the parent's previous position.
140 var nodeEnter = node.enter().append('g')
141 .attr({
142 class: d => `node ${d.type}`,
143 transform: d => `translate(${source.y0},${source.x0})` // this is the starting position
144 });
145
Matteo Scandolo39f49472016-01-25 13:55:09 -0800146 // TODO append different shapes base on type
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800147 nodeEnter.append('circle')
148 .attr('r', 1e-6)
149 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
150 .on('click', serviceClick);
151
152 nodeEnter.append('text')
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800153 .attr({
154 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
155 dy: '.35em',
156 transform: d => {
157 if (d.children && d.parent){
158 if(d.parent.x < d.x){
159 return 'rotate(-30)';
160 }
161 return 'rotate(30)';
162 }
163 },
164 'text-anchor': d => d.children ? 'end' : 'start'
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800165 })
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800166 .text(d => d.name)
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800167 .style('fill-opacity', 1e-6);
168
169 // Transition nodes to their new position.
170 var nodeUpdate = node.transition()
171 .duration(serviceTopologyConfig.duration)
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800172 .attr({
173 'transform': d => `translate(${d.y},${d.x})`
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800174 });
175
176 nodeUpdate.select('circle')
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800177 .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
178 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800179
180 nodeUpdate.select('text')
181 .style('fill-opacity', 1);
182
183 // Transition exiting nodes to the parent's new position.
184 var nodeExit = node.exit().transition()
185 .duration(serviceTopologyConfig.duration)
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800186 .attr({
187 'transform': d => `translate(${source.y},${source.x})`
188 })
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800189 .remove();
190
191 nodeExit.select('circle')
192 .attr('r', 1e-6);
193
194 nodeExit.select('text')
195 .style('fill-opacity', 1e-6);
196
197 // Update the links…
198 var link = svg.selectAll('path.link')
199 .data(links, function(d) { return d.target.id; });
200
201 // Enter any new links at the parent's previous position.
202 link.enter().insert('path', 'g')
Matteo Scandolo39f49472016-01-25 13:55:09 -0800203 .attr('class', d => `link ${d.target.type}`)
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800204 .attr('d', function(d) {
205 var o = {x: source.x0, y: source.y0};
206 return diagonal({source: o, target: o});
207 });
208
209 // Transition links to their new position.
210 link.transition()
211 .duration(serviceTopologyConfig.duration)
212 .attr('d', diagonal);
213
214 // Transition exiting nodes to the parent's new position.
215 link.exit().transition()
216 .duration(serviceTopologyConfig.duration)
217 .attr('d', function(d) {
218 var o = {x: source.x, y: source.y};
219 return diagonal({source: o, target: o});
220 })
221 .remove();
222
223 // Stash the old positions for transition.
224 nodes.forEach(function(d) {
225 d.x0 = d.x;
226 d.y0 = d.y;
227 });
228 };
229
230 const serviceClick = function(d) {
231
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800232 if(!d.service){
233 return;
234 }
235
Matteo Scandolo39f49472016-01-25 13:55:09 -0800236 // toggling selected status
237 d.selected = !d.selected;
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800238
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800239 var selectedNode = d3.select(this);
240
241 selectedNode
242 .transition()
243 .duration(serviceTopologyConfig.duration)
Matteo Scandolo7dbb1912016-01-25 14:11:10 -0800244 .attr('r', serviceTopologyConfig.circle.selectedRadius);
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800245
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800246 ServiceRelation.getServiceInterfaces(d.service.id)
247 .then(interfaceTree => {
248
249 const isDetailed = lodash.find(d.children, {type: 'slice'});
250 if(isDetailed){
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800251 lodash.remove(d.children, {type: 'slice'});
252 }
253 else {
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800254 d.children = d.children.concat(interfaceTree);
255 }
256
257 updateTree(_svg, _layout, _source);
Matteo Scandolo4889f5a2016-01-25 12:00:42 -0800258 });
259 };
260
261 this.updateTree = updateTree;
Matteo Scandolo65d6fc42016-01-25 16:24:42 -0800262 this.drawLegend = drawLegend;
Matteo Scandolofb46f5b2016-01-25 10:10:38 -0800263 });
Matteo Scandolobe9b13d2016-01-21 11:21:03 -0800264
265}());