blob: 9452e5fe112abdd759a1c969283c122c593d18c8 [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 Scandolo071ef462016-01-25 12:00:42 -080010 var _svg, _layout, _source;
11
12 var i = 0;
13
14 // given a canvas, a layout and a data source, draw a tree layout
15 const updateTree = (svg, layout, source) => {
16
17 //cache data
18 _svg = svg;
19 _layout = layout;
20 _source = source;
21
22 const maxDepth = ServiceRelation.depthOf(source);
23
24 const diagonal = d3.svg.diagonal()
25 .projection(d => [d.y, d.x]);
26
27 // Compute the new tree layout.
28 var nodes = layout.nodes(source).reverse(),
29 links = layout.links(nodes);
30
31 // Normalize for fixed-depth.
32 nodes.forEach(function(d) {
33 // position the child node horizontally
34 d.y = d.depth * (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
35 console.log(d.id);
36 if(d.type == 'slice'){
37 console.info('slice found!', d);
38 }
39 });
40
41 // Update the nodes…
42 var node = svg.selectAll('g.node')
43 .data(nodes, function(d) { return d.id || (d.id = ++i); });
44
45 // Enter any new nodes at the parent's previous position.
46 var nodeEnter = node.enter().append('g')
47 .attr({
48 class: d => `node ${d.type}`,
49 transform: d => `translate(${source.y0},${source.x0})` // this is the starting position
50 });
51
52 nodeEnter.append('circle')
53 .attr('r', 1e-6)
54 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
55 .on('click', serviceClick);
56
57 nodeEnter.append('text')
58 .attr('x', function(d) { return d.children || d._children ? -13 : 13; })
59 .attr('transform', function(d) {
60 if((d.children || d._children) && d.parent || d._parent){
61 return 'rotate(30)';
62 }
63 })
64 .attr('dy', '.35em')
65 .attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })
66 .text(function(d) { return d.name; })
67 .style('fill-opacity', 1e-6);
68
69 // Transition nodes to their new position.
70 var nodeUpdate = node.transition()
71 .duration(serviceTopologyConfig.duration)
72 .attr('transform', function(d) {
73 return 'translate(' + d.y + ',' + d.x + ')';
74 });
75
76 nodeUpdate.select('circle')
77 .attr('r', d => d.selected ? 15 : 10)
78 .style('fill', function(d) { return d._children ? 'lightsteelblue' : '#fff'; });
79
80 nodeUpdate.select('text')
81 .style('fill-opacity', 1);
82
83 // Transition exiting nodes to the parent's new position.
84 var nodeExit = node.exit().transition()
85 .duration(serviceTopologyConfig.duration)
86 .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })
87 .remove();
88
89 nodeExit.select('circle')
90 .attr('r', 1e-6);
91
92 nodeExit.select('text')
93 .style('fill-opacity', 1e-6);
94
95 // Update the links…
96 var link = svg.selectAll('path.link')
97 .data(links, function(d) { return d.target.id; });
98
99 // Enter any new links at the parent's previous position.
100 link.enter().insert('path', 'g')
101 .attr('class', 'link')
102 .attr('d', function(d) {
103 var o = {x: source.x0, y: source.y0};
104 return diagonal({source: o, target: o});
105 });
106
107 // Transition links to their new position.
108 link.transition()
109 .duration(serviceTopologyConfig.duration)
110 .attr('d', diagonal);
111
112 // Transition exiting nodes to the parent's new position.
113 link.exit().transition()
114 .duration(serviceTopologyConfig.duration)
115 .attr('d', function(d) {
116 var o = {x: source.x, y: source.y};
117 return diagonal({source: o, target: o});
118 })
119 .remove();
120
121 // Stash the old positions for transition.
122 nodes.forEach(function(d) {
123 d.x0 = d.x;
124 d.y0 = d.y;
125 });
126 };
127
128 const serviceClick = function(d) {
129
130 // empty panel
131 //_this.slices = [];
132 //_this.instances = [];
133
134 // reset all the nodes to default radius
135 var nodes = d3.selectAll('circle')
136 .transition()
137 .duration(serviceTopologyConfig.duration)
138 .attr('r', 10);
139
140 // remove slices details
141 d3.selectAll('rect.slice-detail')
142 .remove();
143 d3.selectAll('text.slice-name')
144 .remove();
145
146 var selectedNode = d3.select(this);
147
148 selectedNode
149 .transition()
150 .duration(serviceTopologyConfig.duration)
151 .attr('r', 15);
152
153 if(!d.service){
154 return;
155 }
156
157 //_this.selectedService = {
158 // id: d.id,
159 // name: d.name
160 //};
161
162 ServiceRelation.getServiceInterfaces(d.service.id)
163 .then(interfaceTree => {
164
165 const isDetailed = lodash.find(d.children, {type: 'slice'});
166 if(isDetailed){
167 d.selected = false;
168 lodash.remove(d.children, {type: 'slice'});
169 }
170 else {
171 d.selected = true;
172
173 d.children = d.children.concat(interfaceTree);
174 }
175
176 updateTree(_svg, _layout, _source);
177 // draw a rect with slice names
178 //const parentNode = d3.select(this.parentNode);
179 //parentNode
180 // .append('rect')
181 // .style('opacity', 0)
182 // .attr({
183 // width: 150,
184 // height: 50,
185 // y: 35,
186 // x: -75,
187 // class: 'slice-detail'
188 // })
189 // .transition()
190 // .duration(serviceTopologyConfig.duration)
191 // .style('opacity', 1);
192 // TODO attach a click listener to draw instances and networks
193
194 //parentNode
195 // .append('text')
196 // .style('opacity', 0)
197 // .attr({
198 // y: 65,
199 // x: -60,
200 // class: 'slice-name'
201 // })
202 // .text(() => {
203 // if(slices[0]){
204 // return slices[0].humanReadableName;
205 // }
206 //
207 // return '';
208 // })
209 // .transition()
210 // .duration(serviceTopologyConfig.duration)
211 // .style('opacity', 1);
212 });
213 };
214
215 this.updateTree = updateTree;
Matteo Scandolof2c99012016-01-25 10:10:38 -0800216 });
Matteo Scandolo8a64fa42016-01-21 11:21:03 -0800217
218}());