blob: ecb8ade0115171dccb620280bd343d6bf8629722 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandolo219b1a72016-02-09 11:19:22 -080019(function () {
20 'use strict';
21
Matteo Scandolo04564952016-02-24 11:22:48 -080022 angular.module('xos.diagnostic')
Matteo Scandolofe307b12016-05-17 14:29:01 -070023 .service('ServiceTopologyHelper', function($rootScope, $window, $log, _, ServiceRelation, serviceTopologyConfig, d3){
Matteo Scandolo219b1a72016-02-09 11:19:22 -080024
Matteo Scandolo574c73f2016-03-01 17:08:45 -080025 var _svg, _layout, _source, _el;
Matteo Scandolo219b1a72016-02-09 11:19:22 -080026
27 var i = 0;
28
29 // given a canvas, a layout and a data source, draw a tree layout
Matteo Scandolo574c73f2016-03-01 17:08:45 -080030 const updateTree = (svg, layout, source, el = _el) => {
Matteo Scandolo574c73f2016-03-01 17:08:45 -080031 if(el){
32 _el = el;
33 }
Matteo Scandolo79108192016-03-08 09:33:26 -080034
35 let targetWidth = _el.clientWidth - serviceTopologyConfig.widthMargin * 2;
Matteo Scandolo219b1a72016-02-09 11:19:22 -080036
37 //cache data
38 _svg = svg;
39 _layout = layout;
40 _source = source;
41
42 const maxDepth = ServiceRelation.depthOf(source);
43
44 const diagonal = d3.svg.diagonal()
45 .projection(d => [d.y, d.x]);
46
47 // Compute the new tree layout.
48 var nodes = layout.nodes(source).reverse(),
49 links = layout.links(nodes);
50
51 // Normalize for fixed-depth.
52 nodes.forEach(function(d) {
53 // position the child node horizontally
Matteo Scandolo79108192016-03-08 09:33:26 -080054 const step = ((targetWidth - (serviceTopologyConfig.widthMargin * 2)) / (maxDepth - 1));
Matteo Scandolo219b1a72016-02-09 11:19:22 -080055 d.y = d.depth * step;
56 });
57
58 // Update the nodes…
59 var node = svg.selectAll('g.node')
60 .data(nodes, function(d) { return d.id || (d.id = ++i); });
61
62 // Enter any new nodes at the parent's previous position.
63 var nodeEnter = node.enter().append('g')
64 .attr({
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080065 class: d => {
Matteo Scandolo77d8fa02016-02-16 17:43:00 -080066 return `node ${d.type}`
67 },
Matteo Scandolo26d17e12016-02-23 13:47:14 -080068 transform: d => (d.x && d.y) ? `translate(${d.y}, ${d.x})` : `translate(${source.y0}, ${source.x0})`
Matteo Scandolo219b1a72016-02-09 11:19:22 -080069 });
70
71 const subscriberNodes = nodeEnter.filter('.subscriber');
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080072 const internetNodes = nodeEnter.filter('.router');
Matteo Scandolo219b1a72016-02-09 11:19:22 -080073 const serviceNodes = nodeEnter.filter('.service');
Matteo Scandolo219b1a72016-02-09 11:19:22 -080074
75 subscriberNodes.append('rect')
Matteo Scandolo574c73f2016-03-01 17:08:45 -080076 .attr(serviceTopologyConfig.square)
77 // add event listener to subscriber
78 .on('click', () => {
79 $rootScope.$emit('subscriber.modal.open');
80 });
Matteo Scandolo219b1a72016-02-09 11:19:22 -080081
82 internetNodes.append('rect')
83 .attr(serviceTopologyConfig.square);
84
85 serviceNodes.append('circle')
86 .attr('r', 1e-6)
87 .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
88 .on('click', serviceClick);
89
Matteo Scandolo219b1a72016-02-09 11:19:22 -080090 nodeEnter.append('text')
91 .attr({
Matteo Scandolof81130f2016-03-11 11:16:58 -080092 x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -5 : serviceTopologyConfig.circle.selectedRadius + 5,
Matteo Scandolo219b1a72016-02-09 11:19:22 -080093 dy: '.35em',
Matteo Scandolof81130f2016-03-11 11:16:58 -080094 y: d => {
95 if (d.children && d.parent){
96 return '-5';
97 }
98 },
Matteo Scandolo219b1a72016-02-09 11:19:22 -080099 transform: d => {
100 if (d.children && d.parent){
101 if(d.parent.x < d.x){
102 return 'rotate(-30)';
103 }
104 return 'rotate(30)';
105 }
106 },
107 'text-anchor': d => d.children ? 'end' : 'start'
108 })
109 .text(d => d.name)
110 .style('fill-opacity', 1e-6);
111
112 // Transition nodes to their new position.
113 var nodeUpdate = node.transition()
114 .duration(serviceTopologyConfig.duration)
115 .attr({
116 'transform': d => `translate(${d.y},${d.x})`
117 });
118
119 nodeUpdate.select('circle')
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800120 .attr('r', d => {
121 return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius
122 })
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800123 .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
124
125 nodeUpdate.select('text')
126 .style('fill-opacity', 1);
127
128 // Transition exiting nodes to the parent's new position.
129 var nodeExit = node.exit().transition()
130 .duration(serviceTopologyConfig.duration)
131 .remove();
132
133 nodeExit.select('circle')
134 .attr('r', 1e-6);
135
136 nodeExit.select('text')
137 .style('fill-opacity', 1e-6);
138
139 // Update the links…
140 var link = svg.selectAll('path.link')
141 .data(links, function(d) { return d.target.id; });
142
143 // Enter any new links at the parent's previous position.
144 link.enter().insert('path', 'g')
145 .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
146 .attr('d', function(d) {
147 var o = {x: source.x0, y: source.y0};
148 return diagonal({source: o, target: o});
149 });
150
151 // Transition links to their new position.
152 link.transition()
153 .duration(serviceTopologyConfig.duration)
154 .attr('d', diagonal);
155
156 // Transition exiting nodes to the parent's new position.
157 link.exit().transition()
158 .duration(serviceTopologyConfig.duration)
159 .attr('d', function(d) {
160 var o = {x: source.x, y: source.y};
161 return diagonal({source: o, target: o});
162 })
163 .remove();
164
165 // Stash the old positions for transition.
166 nodes.forEach(function(d) {
167 d.x0 = d.x;
168 d.y0 = d.y;
169 });
170 };
171
172 const serviceClick = function(d) {
173
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800174 // if was selected
175 if(d.selected){
176 d.selected = !d.selected;
177 $rootScope.$emit('instance.detail.hide', {});
178 return updateTree(_svg, _layout, _source);
179 }
Matteo Scandolo45fba732016-02-22 14:53:44 -0800180
Matteo Scandolo012dddb2016-02-22 16:53:22 -0800181 $rootScope.$emit('instance.detail', {name: d.name, service: d.service, tenant: d.tenant});
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800182
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800183 // unselect all
184 _svg.selectAll('circle')
185 .each(d => d.selected = false);
186
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800187 // toggling selected status
188 d.selected = !d.selected;
189
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800190 updateTree(_svg, _layout, _source);
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800191 };
192
193 this.updateTree = updateTree;
Matteo Scandolo219b1a72016-02-09 11:19:22 -0800194 });
195
196}());