blob: bb7ba5d50f9ecc8da876c860bbe01af24eacbc7b [file] [log] [blame]
(function () {
'use strict';
angular.module('xos.diagnostic')
.service('ServiceTopologyHelper', function($rootScope, $window, $log, lodash, ServiceRelation, serviceTopologyConfig, d3){
var _svg, _layout, _source, _el;
var i = 0;
// given a canvas, a layout and a data source, draw a tree layout
const updateTree = (svg, layout, source, el = _el) => {
if(el){
_el = el;
}
let targetWidth = _el.clientWidth - serviceTopologyConfig.widthMargin * 2;
//cache data
_svg = svg;
_layout = layout;
_source = source;
const maxDepth = ServiceRelation.depthOf(source);
const diagonal = d3.svg.diagonal()
.projection(d => [d.y, d.x]);
// Compute the new tree layout.
var nodes = layout.nodes(source).reverse(),
links = layout.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
// position the child node horizontally
const step = ((targetWidth - (serviceTopologyConfig.widthMargin * 2)) / (maxDepth - 1));
d.y = d.depth * step;
});
// Update the nodes…
var node = svg.selectAll('g.node')
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr({
class: d => {
return `node ${d.type}`
},
transform: d => (d.x && d.y) ? `translate(${d.y}, ${d.x})` : `translate(${source.y0}, ${source.x0})`
});
const subscriberNodes = nodeEnter.filter('.subscriber');
const internetNodes = nodeEnter.filter('.router');
const serviceNodes = nodeEnter.filter('.service');
subscriberNodes.append('rect')
.attr(serviceTopologyConfig.square)
// add event listener to subscriber
.on('click', () => {
$rootScope.$emit('subscriber.modal.open');
});
internetNodes.append('rect')
.attr(serviceTopologyConfig.square);
serviceNodes.append('circle')
.attr('r', 1e-6)
.style('fill', d => d._children ? 'lightsteelblue' : '#fff')
.on('click', serviceClick);
nodeEnter.append('text')
.attr({
x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -5 : serviceTopologyConfig.circle.selectedRadius + 5,
dy: '.35em',
y: d => {
if (d.children && d.parent){
return '-5';
}
},
transform: d => {
if (d.children && d.parent){
if(d.parent.x < d.x){
return 'rotate(-30)';
}
return 'rotate(30)';
}
},
'text-anchor': d => d.children ? 'end' : 'start'
})
.text(d => d.name)
.style('fill-opacity', 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(serviceTopologyConfig.duration)
.attr({
'transform': d => `translate(${d.y},${d.x})`
});
nodeUpdate.select('circle')
.attr('r', d => {
return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius
})
.style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
nodeUpdate.select('text')
.style('fill-opacity', 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(serviceTopologyConfig.duration)
.remove();
nodeExit.select('circle')
.attr('r', 1e-6);
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// Update the links…
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert('path', 'g')
.attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
.attr('d', function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(serviceTopologyConfig.duration)
.attr('d', diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(serviceTopologyConfig.duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
};
const serviceClick = function(d) {
// if was selected
if(d.selected){
d.selected = !d.selected;
$rootScope.$emit('instance.detail.hide', {});
return updateTree(_svg, _layout, _source);
}
$rootScope.$emit('instance.detail', {name: d.name, service: d.service, tenant: d.tenant});
// unselect all
_svg.selectAll('circle')
.each(d => d.selected = false);
// toggling selected status
d.selected = !d.selected;
updateTree(_svg, _layout, _source);
};
this.updateTree = updateTree;
});
}());