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