blob: 027f08d6a92a5cec21426e7c23ec47024f1b27f2 [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 Scandolofcdbed32016-02-08 16:55:44 -08008 .service('TreeLayout', function($window, $log, lodash, ServiceRelation, serviceTopologyConfig){
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
94 // given a canvas, a layout and a data source, draw a tree layout
95 const updateTree = (svg, layout, source) => {
96
97 //cache data
98 _svg = svg;
99 _layout = layout;
100 _source = source;
101
102 const maxDepth = ServiceRelation.depthOf(source);
103
104 const diagonal = d3.svg.diagonal()
105 .projection(d => [d.y, d.x]);
106
107 // Compute the new tree layout.
108 var nodes = layout.nodes(source).reverse(),
109 links = layout.links(nodes);
110
111 // Normalize for fixed-depth.
112 nodes.forEach(function(d) {
113 // position the child node horizontally
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800114 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
115 d.y = d.depth * step;
Matteo Scandolo071ef462016-01-25 12:00:42 -0800116 });
117
118 // Update the nodes…
119 var node = svg.selectAll('g.node')
120 .data(nodes, function(d) { return d.id || (d.id = ++i); });
121
122 // Enter any new nodes at the parent's previous position.
123 var nodeEnter = node.enter().append('g')
124 .attr({
125 class: d => `node ${d.type}`,
Matteo Scandolofcdbed32016-02-08 16:55:44 -0800126 transform: `translate(${source.y0}, ${source.x0})`
Matteo Scandolo071ef462016-01-25 12:00:42 -0800127 });
Matteo Scandolo21c14612016-01-25 17:46:37 -0800128
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800129 const subscriberNodes = nodeEnter.filter('.subscriber');
130 const internetNodes = nodeEnter.filter('.internet');
131 const serviceNodes = nodeEnter.filter('.service');
132 const instanceNodes = nodeEnter.filter('.instance');
133 const sliceNodes = nodeEnter.filter('.slice');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800134
Matteo Scandolofcdbed32016-02-08 16:55:44 -0800135 subscriberNodes.append('rect')
136 .attr(serviceTopologyConfig.square);
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800137
Matteo Scandolofcdbed32016-02-08 16:55:44 -0800138 internetNodes.append('rect')
139 .attr(serviceTopologyConfig.square);
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800140
141 serviceNodes.append('circle')
Matteo Scandolo071ef462016-01-25 12:00:42 -0800142 .attr('r', 1e-6)
143 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
144 .on('click', serviceClick);
145
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800146 sliceNodes.append('rect')
147 .attr({
148 width: 20,
149 height: 20,
150 x: -10,
151 y: -10
152 });
153
154 instanceNodes.append('rect')
155 .attr({
156 width: 20,
157 height: 20,
158 x: -10,
Matteo Scandoloc9ebd922016-01-28 12:02:57 -0800159 y: -10,
160 class: d => d.active ?'' : 'active'
Matteo Scandolo5bf04572016-01-25 17:36:08 -0800161 });
162
Matteo Scandolo071ef462016-01-25 12:00:42 -0800163 nodeEnter.append('text')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800164 .attr({
165 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
166 dy: '.35em',
167 transform: d => {
168 if (d.children && d.parent){
169 if(d.parent.x < d.x){
170 return 'rotate(-30)';
171 }
172 return 'rotate(30)';
173 }
174 },
175 'text-anchor': d => d.children ? 'end' : 'start'
Matteo Scandolo071ef462016-01-25 12:00:42 -0800176 })
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800177 .text(d => d.name)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800178 .style('fill-opacity', 1e-6);
179
180 // Transition nodes to their new position.
181 var nodeUpdate = node.transition()
182 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800183 .attr({
184 'transform': d => `translate(${d.y},${d.x})`
Matteo Scandolo071ef462016-01-25 12:00:42 -0800185 });
186
187 nodeUpdate.select('circle')
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800188 .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
189 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
Matteo Scandolo071ef462016-01-25 12:00:42 -0800190
191 nodeUpdate.select('text')
192 .style('fill-opacity', 1);
193
194 // Transition exiting nodes to the parent's new position.
195 var nodeExit = node.exit().transition()
196 .duration(serviceTopologyConfig.duration)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800197 .remove();
198
199 nodeExit.select('circle')
200 .attr('r', 1e-6);
201
202 nodeExit.select('text')
203 .style('fill-opacity', 1e-6);
204
205 // Update the links…
206 var link = svg.selectAll('path.link')
207 .data(links, function(d) { return d.target.id; });
208
209 // Enter any new links at the parent's previous position.
210 link.enter().insert('path', 'g')
Matteo Scandoloc9ebd922016-01-28 12:02:57 -0800211 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
Matteo Scandolo071ef462016-01-25 12:00:42 -0800212 .attr('d', function(d) {
213 var o = {x: source.x0, y: source.y0};
214 return diagonal({source: o, target: o});
215 });
216
217 // Transition links to their new position.
218 link.transition()
219 .duration(serviceTopologyConfig.duration)
220 .attr('d', diagonal);
221
222 // Transition exiting nodes to the parent's new position.
223 link.exit().transition()
224 .duration(serviceTopologyConfig.duration)
225 .attr('d', function(d) {
226 var o = {x: source.x, y: source.y};
227 return diagonal({source: o, target: o});
228 })
229 .remove();
230
231 // Stash the old positions for transition.
232 nodes.forEach(function(d) {
233 d.x0 = d.x;
234 d.y0 = d.y;
235 });
236 };
237
238 const serviceClick = function(d) {
239
Matteo Scandolofcdbed32016-02-08 16:55:44 -0800240 $log.info('TODO emit an event to highlight VMs');
241
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800242 if(!d.service){
243 return;
244 }
245
Matteo Scandolo9ef3c842016-01-25 13:55:09 -0800246 // toggling selected status
247 d.selected = !d.selected;
Matteo Scandolo071ef462016-01-25 12:00:42 -0800248
Matteo Scandolo071ef462016-01-25 12:00:42 -0800249 var selectedNode = d3.select(this);
250
251 selectedNode
252 .transition()
253 .duration(serviceTopologyConfig.duration)
Matteo Scandolocb12a1a2016-01-25 14:11:10 -0800254 .attr('r', serviceTopologyConfig.circle.selectedRadius);
Matteo Scandolo071ef462016-01-25 12:00:42 -0800255 };
256
257 this.updateTree = updateTree;
Matteo Scandolo2c33a4c2016-01-25 16:24:42 -0800258 this.drawLegend = drawLegend;
Matteo Scandolof2c99012016-01-25 10:10:38 -0800259 });
Matteo Scandolo8a64fa42016-01-21 11:21:03 -0800260
261}());