blob: dab5c3faa6a5f00b8dddc6a0ac4ba4c007096bea [file] [log] [blame]
(function () {
'use strict';
angular.module('xos.serviceTopology')
.directive('serviceCanvas', function(){
return {
restrict: 'E',
scope: {},
bindToController: true,
controllerAs: 'vm',
templateUrl: 'templates/topology_canvas.tpl.html',
controller: function($element, $window, d3, serviceTopologyConfig, ServiceRelation, Slice, Instances, Subscribers){
this.instances = [];
this.slices = [];
// count the mas depth of an object
const depthOf = (obj) => {
var depth = 0;
if (obj.children) {
obj.children.forEach(function (d) {
var tmpDepth = depthOf(d);
if (tmpDepth > depth) {
depth = tmpDepth
}
})
}
return 1 + depth
};
const width = $window.innerWidth - serviceTopologyConfig.widthMargin;
const height = $window.innerHeight - serviceTopologyConfig.heightMargin;
const tree = d3.layout.tree()
.size([height, width]);
const diagonal = d3.svg.diagonal()
.projection(d => [d.y, d.x]);
const svg = d3.select($element[0])
.append('svg')
.style('width', `${$window.innerWidth}px`)
.style('height', `${$window.innerHeight}px`)
.append('g')
.attr("transform", "translate(" + serviceTopologyConfig.widthMargin+ "," + serviceTopologyConfig.heightMargin + ")");;
//const resizeCanvas = () => {
// var targetSize = svg.node().getBoundingClientRect();
//
// d3.select(self.frameElement)
// .attr('width', `${targetSize.width}px`)
// .attr('height', `${targetSize.height}px`)
//};
//d3.select(window)
// .on('load', () => {
// resizeCanvas();
// });
//d3.select(window)
// .on('resize', () => {
// resizeCanvas();
// update(root);
// });
var root;
var i = 0;
var duration = 750;
const draw = (tree) => {
root = tree;
root.x0 = $window.innerHeight / 2;
root.y0 = 0;
update(root);
};
function update(source) {
const maxDepth = depthOf(source);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
// position the child node horizontally
d.y = d.depth * (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
});
// 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', 'node')
.attr('transform', function(d) {
// this is the starting position
return 'translate(' + source.y0 + ',' + source.x0 + ')';
});
nodeEnter.append('circle')
.attr('r', 1e-6)
.style('fill', function(d) { return d._children ? 'lightsteelblue' : '#fff'; })
.on('click', click);
nodeEnter.append('text')
.attr('x', function(d) { return d.children || d._children ? -13 : 13; })
.attr('transform', function(d) {
if((d.children || d._children) && d.parent || d._parent){
return 'rotate(30)';
}
return;
})
.attr('dy', '.35em')
.attr('text-anchor', function(d) { return d.children || d._children ? 'end' : 'start'; })
.text(function(d) { return d.name; })
.style('fill-opacity', 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
});
nodeUpdate.select('circle')
.attr('r', 10)
.style('fill', function(d) { return d._children ? 'lightsteelblue' : '#fff'; });
nodeUpdate.select('text')
.style('fill-opacity', 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; })
.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', 'link')
.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(duration)
.attr('d', diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(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;
});
}
var _this = this;
const click = function(d) {
// empty panel
_this.slices = [];
_this.instances = [];
var nodes = d3.selectAll('circle')
.transition()
.duration(duration)
.attr('r', 10);
d3.selectAll('rect.slice-detail')
.remove();
d3.selectAll('text.slice-name')
.remove();
var selectedNode = d3.select(this);
selectedNode
.transition()
.duration(duration)
.attr('r', 30);
if(!d.service){
return;
}
_this.selectedService = {
id: d.id,
name: d.name
};
Slice.query({service: d.service.id}).$promise
.then(slices => {
_this.slices = slices;
if(slices.length > 0){
const parentNode = d3.select(this.parentNode);
parentNode
.append('rect')
.style('opacity', 0)
.attr({
width: 150,
height: 50,
y: 35,
x: -75,
class: 'slice-detail'
})
.transition()
.duration(duration)
.style('opacity', 1);
parentNode
.append('text')
.style('opacity', 0)
.attr({
y: 65,
x: -60,
class: 'slice-name'
})
.text(() => {
if(slices[0]){
return slices[0].humanReadableName;
}
return '';
})
.transition()
.duration(duration)
.style('opacity', 1);
}
})
};
Subscribers.query().$promise
.then((subscribers) => {
this.subscribers = subscribers;
});
this.getInstances = (slice) => {
Instances.query({slice: slice.id}).$promise
.then((instances) => {
this.selectedSlice = slice;
this.instances = instances;
})
};
this.getServiceChain = (subscriber) => {
ServiceRelation.get(subscriber)
.then((tree) => {
draw(tree);
});
};
}
}
});
}());