Base tree layout
diff --git a/gui/ngXosViews/serviceTopology/src/js/config.js b/gui/ngXosViews/serviceTopology/src/js/config.js
new file mode 100644
index 0000000..b5c0db0
--- /dev/null
+++ b/gui/ngXosViews/serviceTopology/src/js/config.js
@@ -0,0 +1,10 @@
+(function () {
+ 'use strict';
+
+ angular.module('xos.serviceTopology')
+ .constant('serviceTopologyConfig', {
+ widthMargin: 100,
+ heightMargin: 30
+ })
+
+}());
\ No newline at end of file
diff --git a/gui/ngXosViews/serviceTopology/src/js/d3.js b/gui/ngXosViews/serviceTopology/src/js/d3.js
new file mode 100644
index 0000000..d87ecda
--- /dev/null
+++ b/gui/ngXosViews/serviceTopology/src/js/d3.js
@@ -0,0 +1,9 @@
+(function () {
+ 'use strict';
+
+ angular.module('xos.serviceTopology')
+ .factory('d3', function($window){
+ return $window.d3;
+ })
+
+}());
\ No newline at end of file
diff --git a/gui/ngXosViews/serviceTopology/src/js/main.js b/gui/ngXosViews/serviceTopology/src/js/main.js
new file mode 100644
index 0000000..1d86d86
--- /dev/null
+++ b/gui/ngXosViews/serviceTopology/src/js/main.js
@@ -0,0 +1,38 @@
+'use strict';
+
+angular.module('xos.serviceTopology', [
+ 'ngResource',
+ 'ngCookies',
+ 'ngLodash',
+ 'ui.router',
+ 'xos.helpers'
+])
+.config(($stateProvider) => {
+ $stateProvider
+ .state('user-list', {
+ url: '/',
+ template: '<service-canvas></service-canvas>'
+ });
+})
+.config(function($httpProvider){
+ $httpProvider.interceptors.push('NoHyperlinks');
+})
+.directive('usersList', function(){
+ return {
+ restrict: 'E',
+ scope: {},
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/users-list.tpl.html',
+ controller: function(Services){
+ // retrieving user list
+ Services.query().$promise
+ .then((res) => {
+ console.log(res);
+ })
+ .catch((e) => {
+ throw new Error(e);
+ });
+ }
+ };
+});
\ No newline at end of file
diff --git a/gui/ngXosViews/serviceTopology/src/js/services.js b/gui/ngXosViews/serviceTopology/src/js/services.js
new file mode 100644
index 0000000..93a1edb
--- /dev/null
+++ b/gui/ngXosViews/serviceTopology/src/js/services.js
@@ -0,0 +1,9 @@
+(function () {
+ 'use strict';
+
+ angular.module('xos.serviceTopology')
+ .service('Services', function($resource){
+ return $resource('/xos/services');
+ });
+
+}());
\ No newline at end of file
diff --git a/gui/ngXosViews/serviceTopology/src/js/topologyCanvas.js b/gui/ngXosViews/serviceTopology/src/js/topologyCanvas.js
new file mode 100644
index 0000000..88d1388
--- /dev/null
+++ b/gui/ngXosViews/serviceTopology/src/js/topologyCanvas.js
@@ -0,0 +1,201 @@
+(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){
+
+ // 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 treeData = [
+ {
+ 'name': 'Top Level',
+ 'parent': 'null',
+ 'children': [
+ {
+ 'name': 'Level 2: A',
+ 'parent': 'Top Level',
+ 'children': [
+ {
+ 'name': 'Son of A',
+ 'parent': 'Level 2: A'
+ },
+ {
+ 'name': 'Daughter of A',
+ 'parent': 'Level 2: A'
+ }
+ ]
+ },
+ {
+ 'name': 'Level 2: B',
+ 'parent': 'Top Level'
+ }
+ ]
+ }
+ ];
+
+ 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 = treeData[0];
+ root.x0 = $window.innerHeight / 2;
+ root.y0 = 0;
+
+ var i = 0;
+ var duration = 750;
+
+ 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) {
+ console.log(d);
+ // 180 should be based on window.width and max node depth
+
+ d.y = d.depth * (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
+ console.log(d.x);
+ });
+
+ // 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 + ')';
+ });
+ //.on('click', click);
+
+ nodeEnter.append('circle')
+ .attr('r', 1e-6)
+ .style('fill', function(d) { return d._children ? 'lightsteelblue' : '#fff'; });
+
+ nodeEnter.append('text')
+ .attr('x', function(d) { return d.children || d._children ? -13 : 13; })
+ .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;
+ });
+ }
+ }
+ }
+ });
+
+}());
\ No newline at end of file