blob: 1d53d728c3006f461d84e794b4e42e1abc5d8578 [file] [log] [blame]
Matteo Scandolo219b1a72016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
Matteo Scandolo04564952016-02-24 11:22:48 -08004 angular.module('xos.diagnostic')
Matteo Scandolo35d53c82016-02-16 14:44:51 -08005 .service('ServiceTopologyHelper', function($rootScope, $window, $log, lodash, ServiceRelation, serviceTopologyConfig, d3){
Matteo Scandolo219b1a72016-02-09 11:19:22 -08006
Matteo Scandolo26d17e12016-02-23 13:47:14 -08007 // NOTE not used anymore
Matteo Scandolo219b1a72016-02-09 11:19:22 -08008 const drawLegend = (svg) => {
9 const legendContainer = svg.append('g')
10 .attr({
11 class: 'legend'
12 });
13
14 legendContainer.append('rect')
15 .attr({
16 transform: d => `translate(10, 80)`,
17 width: 100,
18 height: 100
19 });
20
21 // service
22 const service = legendContainer.append('g')
23 .attr({
24 class: 'node service'
25 });
26
27 service.append('circle')
28 .attr({
29 r: serviceTopologyConfig.circle.radius,
30 transform: d => `translate(30, 100)`
31 });
32
33 service.append('text')
34 .attr({
35 transform: d => `translate(45, 100)`,
36 dy: '.35em'
37 })
38 .text('Service')
39 .style('fill-opacity', 1);
40
41 // slice
42 const slice = legendContainer.append('g')
43 .attr({
44 class: 'node slice'
45 });
46
47 slice.append('rect')
48 .attr({
49 width: 20,
50 height: 20,
51 x: -10,
52 y: -10,
53 transform: d => `translate(30, 130)`
54 });
55
56 slice.append('text')
57 .attr({
58 transform: d => `translate(45, 130)`,
59 dy: '.35em'
60 })
61 .text('Slices')
62 .style('fill-opacity', 1);
63
64 // instance
65 const instance = legendContainer.append('g')
66 .attr({
67 class: 'node instance'
68 });
69
70 instance.append('rect')
71 .attr({
72 width: 20,
73 height: 20,
74 x: -10,
75 y: -10,
76 transform: d => `translate(30, 160)`
77 });
78
79 instance.append('text')
80 .attr({
81 transform: d => `translate(45, 160)`,
82 dy: '.35em'
83 })
84 .text('Instances')
85 .style('fill-opacity', 1);
86 };
87
88 var _svg, _layout, _source;
89
90 var i = 0;
91
92 // given a canvas, a layout and a data source, draw a tree layout
93 const updateTree = (svg, layout, source) => {
94
95 //cache data
96 _svg = svg;
97 _layout = layout;
98 _source = source;
99
100 const maxDepth = ServiceRelation.depthOf(source);
101
102 const diagonal = d3.svg.diagonal()
103 .projection(d => [d.y, d.x]);
104
105 // Compute the new tree layout.
106 var nodes = layout.nodes(source).reverse(),
107 links = layout.links(nodes);
108
109 // Normalize for fixed-depth.
110 nodes.forEach(function(d) {
111 // position the child node horizontally
112 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
113 d.y = d.depth * step;
114 });
115
116 // Update the nodes…
117 var node = svg.selectAll('g.node')
118 .data(nodes, function(d) { return d.id || (d.id = ++i); });
119
120 // Enter any new nodes at the parent's previous position.
121 var nodeEnter = node.enter().append('g')
122 .attr({
Matteo Scandolo77d8fa02016-02-16 17:43:00 -0800123 class: d => {
Matteo Scandolo77d8fa02016-02-16 17:43:00 -0800124 return `node ${d.type}`
125 },
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800126 transform: d => (d.x && d.y) ? `translate(${d.y}, ${d.x})` : `translate(${source.y0}, ${source.x0})`
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800127 });
128
129 const subscriberNodes = nodeEnter.filter('.subscriber');
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800130 const internetNodes = nodeEnter.filter('.router');
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800131 const serviceNodes = nodeEnter.filter('.service');
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800132
133 subscriberNodes.append('rect')
134 .attr(serviceTopologyConfig.square);
135
136 internetNodes.append('rect')
137 .attr(serviceTopologyConfig.square);
138
139 serviceNodes.append('circle')
140 .attr('r', 1e-6)
141 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
142 .on('click', serviceClick);
143
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800144 nodeEnter.append('text')
145 .attr({
146 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
147 dy: '.35em',
148 transform: d => {
149 if (d.children && d.parent){
150 if(d.parent.x < d.x){
151 return 'rotate(-30)';
152 }
153 return 'rotate(30)';
154 }
155 },
156 'text-anchor': d => d.children ? 'end' : 'start'
157 })
158 .text(d => d.name)
159 .style('fill-opacity', 1e-6);
160
161 // Transition nodes to their new position.
162 var nodeUpdate = node.transition()
163 .duration(serviceTopologyConfig.duration)
164 .attr({
165 'transform': d => `translate(${d.y},${d.x})`
166 });
167
168 nodeUpdate.select('circle')
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800169 .attr('r', d => {
170 return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius
171 })
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800172 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
173
174 nodeUpdate.select('text')
175 .style('fill-opacity', 1);
176
177 // Transition exiting nodes to the parent's new position.
178 var nodeExit = node.exit().transition()
179 .duration(serviceTopologyConfig.duration)
180 .remove();
181
182 nodeExit.select('circle')
183 .attr('r', 1e-6);
184
185 nodeExit.select('text')
186 .style('fill-opacity', 1e-6);
187
188 // Update the links…
189 var link = svg.selectAll('path.link')
190 .data(links, function(d) { return d.target.id; });
191
192 // Enter any new links at the parent's previous position.
193 link.enter().insert('path', 'g')
194 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
195 .attr('d', function(d) {
196 var o = {x: source.x0, y: source.y0};
197 return diagonal({source: o, target: o});
198 });
199
200 // Transition links to their new position.
201 link.transition()
202 .duration(serviceTopologyConfig.duration)
203 .attr('d', diagonal);
204
205 // Transition exiting nodes to the parent's new position.
206 link.exit().transition()
207 .duration(serviceTopologyConfig.duration)
208 .attr('d', function(d) {
209 var o = {x: source.x, y: source.y};
210 return diagonal({source: o, target: o});
211 })
212 .remove();
213
214 // Stash the old positions for transition.
215 nodes.forEach(function(d) {
216 d.x0 = d.x;
217 d.y0 = d.y;
218 });
219 };
220
221 const serviceClick = function(d) {
222
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800223 // if was selected
224 if(d.selected){
225 d.selected = !d.selected;
226 $rootScope.$emit('instance.detail.hide', {});
227 return updateTree(_svg, _layout, _source);
228 }
Matteo Scandolo45fba732016-02-22 14:53:44 -0800229
Matteo Scandolo012dddb2016-02-22 16:53:22 -0800230 $rootScope.$emit('instance.detail', {name: d.name, service: d.service, tenant: d.tenant});
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800231
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800232 // unselect all
233 _svg.selectAll('circle')
234 .each(d => d.selected = false);
235
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800236 // toggling selected status
237 d.selected = !d.selected;
238
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800239 updateTree(_svg, _layout, _source);
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800240 };
241
242 this.updateTree = updateTree;
243 this.drawLegend = drawLegend;
244 });
245
246}());