blob: c903b3beacab0468ba24da85419e3248c0c373c4 [file] [log] [blame]
Matteo Scandoloeeb9c082016-02-09 11:19:22 -08001(function () {
2 'use strict';
3
Matteo Scandolo4b3d8722016-02-24 11:22:48 -08004 angular.module('xos.diagnostic')
Matteo Scandolo78e9d0e2016-05-17 14:29:01 -07005 .service('ServiceTopologyHelper', function($rootScope, $window, $log, _, ServiceRelation, serviceTopologyConfig, d3){
Matteo Scandoloeeb9c082016-02-09 11:19:22 -08006
Matteo Scandolo5bb16682016-03-01 17:08:45 -08007 var _svg, _layout, _source, _el;
Matteo Scandoloeeb9c082016-02-09 11:19:22 -08008
9 var i = 0;
10
11 // given a canvas, a layout and a data source, draw a tree layout
Matteo Scandolo5bb16682016-03-01 17:08:45 -080012 const updateTree = (svg, layout, source, el = _el) => {
Matteo Scandolo5bb16682016-03-01 17:08:45 -080013 if(el){
14 _el = el;
15 }
Matteo Scandolod54a7fd2016-03-08 09:33:26 -080016
17 let targetWidth = _el.clientWidth - serviceTopologyConfig.widthMargin * 2;
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080018
19 //cache data
20 _svg = svg;
21 _layout = layout;
22 _source = source;
23
24 const maxDepth = ServiceRelation.depthOf(source);
25
26 const diagonal = d3.svg.diagonal()
27 .projection(d => [d.y, d.x]);
28
29 // Compute the new tree layout.
30 var nodes = layout.nodes(source).reverse(),
31 links = layout.links(nodes);
32
33 // Normalize for fixed-depth.
34 nodes.forEach(function(d) {
35 // position the child node horizontally
Matteo Scandolod54a7fd2016-03-08 09:33:26 -080036 const step = ((targetWidth - (serviceTopologyConfig.widthMargin * 2)) / (maxDepth - 1));
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080037 d.y = d.depth * step;
38 });
39
40 // Update the nodes…
41 var node = svg.selectAll('g.node')
42 .data(nodes, function(d) { return d.id || (d.id = ++i); });
43
44 // Enter any new nodes at the parent's previous position.
45 var nodeEnter = node.enter().append('g')
46 .attr({
Matteo Scandolo657d1322016-02-16 17:43:00 -080047 class: d => {
Matteo Scandolo657d1322016-02-16 17:43:00 -080048 return `node ${d.type}`
49 },
Matteo Scandolo06afdfe2016-02-23 13:47:14 -080050 transform: d => (d.x && d.y) ? `translate(${d.y}, ${d.x})` : `translate(${source.y0}, ${source.x0})`
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080051 });
52
53 const subscriberNodes = nodeEnter.filter('.subscriber');
Matteo Scandolo38ba3312016-02-09 16:01:49 -080054 const internetNodes = nodeEnter.filter('.router');
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080055 const serviceNodes = nodeEnter.filter('.service');
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080056
57 subscriberNodes.append('rect')
Matteo Scandolo5bb16682016-03-01 17:08:45 -080058 .attr(serviceTopologyConfig.square)
59 // add event listener to subscriber
60 .on('click', () => {
61 $rootScope.$emit('subscriber.modal.open');
62 });
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080063
64 internetNodes.append('rect')
65 .attr(serviceTopologyConfig.square);
66
67 serviceNodes.append('circle')
68 .attr('r', 1e-6)
69 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
70 .on('click', serviceClick);
71
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080072 nodeEnter.append('text')
73 .attr({
Matteo Scandolo105954e2016-03-11 11:16:58 -080074 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -5 : serviceTopologyConfig.circle.selectedRadius + 5,
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080075 dy: '.35em',
Matteo Scandolo105954e2016-03-11 11:16:58 -080076 y: d => {
77 if (d.children && d.parent){
78 return '-5';
79 }
80 },
Matteo Scandoloeeb9c082016-02-09 11:19:22 -080081 transform: d => {
82 if (d.children && d.parent){
83 if(d.parent.x < d.x){
84 return 'rotate(-30)';
85 }
86 return 'rotate(30)';
87 }
88 },
89 'text-anchor': d => d.children ? 'end' : 'start'
90 })
91 .text(d => d.name)
92 .style('fill-opacity', 1e-6);
93
94 // Transition nodes to their new position.
95 var nodeUpdate = node.transition()
96 .duration(serviceTopologyConfig.duration)
97 .attr({
98 'transform': d => `translate(${d.y},${d.x})`
99 });
100
101 nodeUpdate.select('circle')
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800102 .attr('r', d => {
103 return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius
104 })
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800105 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
106
107 nodeUpdate.select('text')
108 .style('fill-opacity', 1);
109
110 // Transition exiting nodes to the parent's new position.
111 var nodeExit = node.exit().transition()
112 .duration(serviceTopologyConfig.duration)
113 .remove();
114
115 nodeExit.select('circle')
116 .attr('r', 1e-6);
117
118 nodeExit.select('text')
119 .style('fill-opacity', 1e-6);
120
121 // Update the links…
122 var link = svg.selectAll('path.link')
123 .data(links, function(d) { return d.target.id; });
124
125 // Enter any new links at the parent's previous position.
126 link.enter().insert('path', 'g')
127 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
128 .attr('d', function(d) {
129 var o = {x: source.x0, y: source.y0};
130 return diagonal({source: o, target: o});
131 });
132
133 // Transition links to their new position.
134 link.transition()
135 .duration(serviceTopologyConfig.duration)
136 .attr('d', diagonal);
137
138 // Transition exiting nodes to the parent's new position.
139 link.exit().transition()
140 .duration(serviceTopologyConfig.duration)
141 .attr('d', function(d) {
142 var o = {x: source.x, y: source.y};
143 return diagonal({source: o, target: o});
144 })
145 .remove();
146
147 // Stash the old positions for transition.
148 nodes.forEach(function(d) {
149 d.x0 = d.x;
150 d.y0 = d.y;
151 });
152 };
153
154 const serviceClick = function(d) {
155
Matteo Scandoloc303fd02016-02-17 15:11:33 -0800156 // if was selected
157 if(d.selected){
158 d.selected = !d.selected;
159 $rootScope.$emit('instance.detail.hide', {});
160 return updateTree(_svg, _layout, _source);
161 }
Matteo Scandolodffc1382016-02-22 14:53:44 -0800162
Matteo Scandolo0344ef32016-02-22 16:53:22 -0800163 $rootScope.$emit('instance.detail', {name: d.name, service: d.service, tenant: d.tenant});
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800164
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800165 // unselect all
166 _svg.selectAll('circle')
167 .each(d => d.selected = false);
168
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800169 // toggling selected status
170 d.selected = !d.selected;
171
Matteo Scandolo7fd4d042016-02-16 14:44:51 -0800172 updateTree(_svg, _layout, _source);
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800173 };
174
175 this.updateTree = updateTree;
Matteo Scandoloeeb9c082016-02-09 11:19:22 -0800176 });
177
178}());