blob: 28794c2aff5170342f478f7d69f6e45c397a7122 [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 Scandolo219b1a72016-02-09 11:19:22 -08007 var _svg, _layout, _source;
8
9 var i = 0;
10
11 // given a canvas, a layout and a data source, draw a tree layout
12 const updateTree = (svg, layout, source) => {
13
14 //cache data
15 _svg = svg;
16 _layout = layout;
17 _source = source;
18
19 const maxDepth = ServiceRelation.depthOf(source);
20
21 const diagonal = d3.svg.diagonal()
22 .projection(d => [d.y, d.x]);
23
24 // Compute the new tree layout.
25 var nodes = layout.nodes(source).reverse(),
26 links = layout.links(nodes);
27
28 // Normalize for fixed-depth.
29 nodes.forEach(function(d) {
30 // position the child node horizontally
31 const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
32 d.y = d.depth * step;
33 });
34
35 // Update the nodes…
36 var node = svg.selectAll('g.node')
37 .data(nodes, function(d) { return d.id || (d.id = ++i); });
38
39 // Enter any new nodes at the parent's previous position.
40 var nodeEnter = node.enter().append('g')
41 .attr({
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080042 class: d => {
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080043 return `node ${d.type}`
44 },
Matteo Scandolo26d17e12016-02-23 13:47:14 -080045 transform: d => (d.x && d.y) ? `translate(${d.y}, ${d.x})` : `translate(${source.y0}, ${source.x0})`
Matteo Scandolo219b1a72016-02-09 11:19:22 -080046 });
47
48 const subscriberNodes = nodeEnter.filter('.subscriber');
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080049 const internetNodes = nodeEnter.filter('.router');
Matteo Scandolo219b1a72016-02-09 11:19:22 -080050 const serviceNodes = nodeEnter.filter('.service');
Matteo Scandolo219b1a72016-02-09 11:19:22 -080051
52 subscriberNodes.append('rect')
53 .attr(serviceTopologyConfig.square);
54
55 internetNodes.append('rect')
56 .attr(serviceTopologyConfig.square);
57
58 serviceNodes.append('circle')
59 .attr('r', 1e-6)
60 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
61 .on('click', serviceClick);
62
Matteo Scandolo219b1a72016-02-09 11:19:22 -080063 nodeEnter.append('text')
64 .attr({
65 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
66 dy: '.35em',
67 transform: d => {
68 if (d.children && d.parent){
69 if(d.parent.x < d.x){
70 return 'rotate(-30)';
71 }
72 return 'rotate(30)';
73 }
74 },
75 'text-anchor': d => d.children ? 'end' : 'start'
76 })
77 .text(d => d.name)
78 .style('fill-opacity', 1e-6);
79
80 // Transition nodes to their new position.
81 var nodeUpdate = node.transition()
82 .duration(serviceTopologyConfig.duration)
83 .attr({
84 'transform': d => `translate(${d.y},${d.x})`
85 });
86
87 nodeUpdate.select('circle')
Matteo Scandolo35d53c82016-02-16 14:44:51 -080088 .attr('r', d => {
89 return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius
90 })
Matteo Scandolo219b1a72016-02-09 11:19:22 -080091 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
92
93 nodeUpdate.select('text')
94 .style('fill-opacity', 1);
95
96 // Transition exiting nodes to the parent's new position.
97 var nodeExit = node.exit().transition()
98 .duration(serviceTopologyConfig.duration)
99 .remove();
100
101 nodeExit.select('circle')
102 .attr('r', 1e-6);
103
104 nodeExit.select('text')
105 .style('fill-opacity', 1e-6);
106
107 // Update the links…
108 var link = svg.selectAll('path.link')
109 .data(links, function(d) { return d.target.id; });
110
111 // Enter any new links at the parent's previous position.
112 link.enter().insert('path', 'g')
113 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
114 .attr('d', function(d) {
115 var o = {x: source.x0, y: source.y0};
116 return diagonal({source: o, target: o});
117 });
118
119 // Transition links to their new position.
120 link.transition()
121 .duration(serviceTopologyConfig.duration)
122 .attr('d', diagonal);
123
124 // Transition exiting nodes to the parent's new position.
125 link.exit().transition()
126 .duration(serviceTopologyConfig.duration)
127 .attr('d', function(d) {
128 var o = {x: source.x, y: source.y};
129 return diagonal({source: o, target: o});
130 })
131 .remove();
132
133 // Stash the old positions for transition.
134 nodes.forEach(function(d) {
135 d.x0 = d.x;
136 d.y0 = d.y;
137 });
138 };
139
140 const serviceClick = function(d) {
141
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800142 // if was selected
143 if(d.selected){
144 d.selected = !d.selected;
145 $rootScope.$emit('instance.detail.hide', {});
146 return updateTree(_svg, _layout, _source);
147 }
Matteo Scandolo45fba732016-02-22 14:53:44 -0800148
Matteo Scandolo012dddb2016-02-22 16:53:22 -0800149 $rootScope.$emit('instance.detail', {name: d.name, service: d.service, tenant: d.tenant});
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800150
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800151 // unselect all
152 _svg.selectAll('circle')
153 .each(d => d.selected = false);
154
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800155 // toggling selected status
156 d.selected = !d.selected;
157
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800158 updateTree(_svg, _layout, _source);
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800159 };
160
161 this.updateTree = updateTree;
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800162 });
163
164}());