| (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); |
| }); |
| }; |
| } |
| } |
| }); |
| |
| }()); |