Displaying service coarse graph

Change-Id: Iee05e9114255a20ebf600337768d180514239299
diff --git a/views/ngXosViews/serviceGrid/src/js/service-graph.js b/views/ngXosViews/serviceGrid/src/js/service-graph.js
index 51a711a..904466c 100644
--- a/views/ngXosViews/serviceGrid/src/js/service-graph.js
+++ b/views/ngXosViews/serviceGrid/src/js/service-graph.js
@@ -2,6 +2,64 @@
   'use strict';
 
   angular.module('xos.serviceGrid')
+  .service('Graph', function($q, Tenants, Services, Subscribers){
+
+    let tenancyGraph = new graphlib.Graph();
+    let cached = false;
+
+    const buildGraph = () => {
+
+      let deferred = $q.defer();
+
+      $q.all([
+        Tenants.query().$promise,
+        Services.query().$promise,
+        Subscribers.query().$promise
+      ])
+      .then((res) => {
+        let [tenants, services, subscribers] = res;
+        // adding service nodes
+        services.forEach(s => tenancyGraph.setNode(s.id, angular.extend(s, {type: 'service'})));
+
+
+        // coarse tenant
+        tenants.filter(t => t.subscriber_service && t.provider_service)
+          .forEach(t => tenancyGraph.setEdge(t.subscriber_service, t.provider_service, t, t.name));
+
+        // fine grain tenant
+        // adding subscribers as nodes (to build fine grain graph)
+        // subscribers.forEach(s => tenancyGraph.setNode(`sub-${s.id}`, angular.extend(s, {type: 'subscriber'})));
+        // TODO
+        // - Find tenant that start from a subscriber
+        // - Follow the chain: from the first tenant follow where subscriber_tenant = tenant_id untill we cannot find any more tenant
+        // tenants.filter(t => t.subscriber_root && t.provider_service)
+        // .forEach(t => tenancyGraph.setEdge(`sub-${t.subscriber_root}`, t.provider_service, t, t.name));
+        
+        deferred.resolve(tenancyGraph);
+      });
+
+      return deferred.promise;
+    };
+
+    this.getGraph = () => {
+      let deferred = $q.defer();
+
+      if(cached){
+        deferred.resolve(tenancyGraph);
+      }
+      else {
+        buildGraph()
+        .then((res) => {
+          cached = true;
+          deferred.resolve(res);
+        })
+        .catch(console.log);
+      }
+
+      return {$promise: deferred.promise};
+    };
+
+  })
   .directive('serviceGraph', function(){
     return {
       restrict: 'E',
@@ -9,54 +67,61 @@
       bindToController: true,
       controllerAs: 'vm',
       templateUrl: 'templates/service-graph.tpl.html',
-      controller: function($element, GraphService){
+      controller: function($scope, $element, GraphService, Graph, ToscaEncoder){
 
         let svg;
         let el = $element[0];
         let node;
         let link;
+        const _this = this;
 
+        this.panelConfig = {
+          position: 'right'
+        };
+
+        // animate node and links in the correct place
         const tick = (e) => {
-          // Push different nodes in different directions for clustering.
-          
           node
-            // .attr('cx', d => d.x)
-            //   .attr('cy', d => d.y)
-              .attr({
-                transform: d => `translate(${d.x}, ${d.y})`
-              });
+          .attr('cx', d => d.x)
+          .attr('cy', d => d.y)
+          .attr({
+            transform: d => `translate(${d.x}, ${d.y})`
+          });
           
-          link.attr('x1', d => d.source.x)
-              .attr('y1', d => d.source.y)
-              .attr('x2', d => d.target.x)
-              .attr('y2', d => d.target.y);
-        }
+          link
+          .attr('x1', d => d.source.x)
+          .attr('y1', d => d.source.y)
+          .attr('x2', d => getTargetNodeCircumferencePoint(d)[0])
+          .attr('y2', d => getTargetNodeCircumferencePoint(d)[1]);
+        };
 
-        GraphService.loadCoarseData()
-        .then((res) => {
+        Graph.getGraph().$promise
+        .then((graph) => {
 
           // build links
-          res.tenants = res.tenants.map(t => {
+          let links = graph.edges().map(e => {
             return {
-              source: t.provider_service,
-              target: t.subscriber_service
+              source: graph.node(e.v),
+              target: graph.node(e.w)
             }
           });
+          let nodes = graph.nodes().map(n => graph.node(n));
 
-          // add xos as a node
-          res.services.push({
-            name: 'XOS',
-            class: 'xos',
-            x: el.clientWidth / 2,
-            y: el.clientHeight / 2,
-            fixed: true
-          })
+           //add xos as a node
+          nodes.push({
+             name: 'XOS',
+             type: 'xos',
+             x: el.clientWidth / 2,
+             y: el.clientHeight / 2,
+             fixed: true
+           });
 
           handleSvg(el);
+          defineArrows();
 
           var force = d3.layout.force()
-            .nodes(res.services)
-            .links(res.tenants)
+            .nodes(nodes)
+            .links(links)
             .charge(-1060)
             .gravity(0.1)
             .linkDistance(200)
@@ -65,38 +130,66 @@
             .start();
 
           link = svg.selectAll('.link')
-          .data(res.tenants).enter().insert('line')
-                .attr('class', 'link');
+            .data(links).enter().insert('line')
+            .attr('class', 'link')
+            .attr('marker-end', 'url(#arrow)');
 
           node = svg.selectAll('.node')
-            .data(res.services)
+            .data(nodes)
             .enter().append('g')
             .call(force.drag)
-            .on("mousedown", function() { d3.event.stopPropagation(); });
+            .on('mousedown', function(d) {
+              $scope.$apply(() => {
+                if(d.name === 'XOS'){
+                  return;
+                }
+                _this.panelShow = true;
+                let status = parseInt(d.backend_status.match(/^[0-9]/)[0]);
+                console.log(status);
+                switch(status){
+                  case 0:
+                    d.icon = 'time';
+                    break;
+                  case 1:
+                    d.icon = 'ok';
+                    break;
+                  case 2:
+                    d.icon = 'remove';
+                    break;
+                }
+                _this.selectedNode = d;
+              });
+              d3.event.stopPropagation();
+            });
 
           node.append('circle')
             .attr({
-              class: d => `node ${d.class || ''}`,
+              class: d => `node ${d.type || ''}`,
               r: 10
             });
 
           node.append('text')
             .attr({
-              'text-anchor': 'middle'
+              'text-anchor': 'middle',
+              'alignment-baseline': 'middle'
             })
-            .text(d => d.name)
+            .text(d => d.humanReadableName || d.name);
 
+          // scale the node to fit the contained text
           node.select('circle')
             .attr({
               r: function(d){
-                let parent = d3.select(this).node().parentNode;
-                let sib = d3.select(parent).select('text').node().getBBox()
-                return (sib.width / 2) + 10
-                
+                const parent = d3.select(this).node().parentNode;
+                const sib = d3.select(parent).select('text').node().getBBox();
+                const radius = (sib.width / 2) + 10;
+
+                // add radius as node attribute
+                d.nodeWidth = radius * 2;
+                return radius;
               }
             })
 
-        })
+        });
 
         const handleSvg = (el) => {
           d3.select(el).select('svg').remove();
@@ -105,76 +198,39 @@
           .append('svg')
           .style('width', `${el.clientWidth}px`)
           .style('height', `${el.clientHeight}px`);
+        };
+
+        const defineArrows = () => {
+          svg.append('svg:defs').selectAll('marker')
+          .data(['arrow'])      // Different link/path types can be defined here
+          .enter().append('svg:marker')    // This section adds in the arrows
+          .attr('id', String)
+          .attr('viewBox', '0 -5 10 10')
+          .attr('refX', 10)
+          .attr('refY', 0)
+          .attr('markerWidth', 6)
+          .attr('markerHeight', 6)
+          .attr('orient', 'auto')
+          .append('svg:path')
+          .attr('d', 'M0,-5L10,0L0,5');
+        };
+
+        const getTargetNodeCircumferencePoint = d => {
+          const radius = d.target.nodeWidth / 2; // nodeWidth is just a custom attribute I calculate during the creation of the nodes depending on the node width
+          const dx = d.target.x - d.source.x;
+          const dy = d.target.y - d.source.y;
+          const gamma = Math.atan2(dy, dx); // Math.atan2 returns the angle in the correct quadrant as opposed to Math.atan
+
+          const tx = d.target.x - (Math.cos(gamma) * radius);
+          const ty = d.target.y - (Math.sin(gamma) * radius);
+
+          return [tx, ty];
+        };
+
+        this.exportToTosca = (service) => {
+          ToscaEncoder.serviceToTosca(service);
         }
       }
     };
   })
-})();
-
-// Draw services around xos and calculate coarse tenant as links
-
-// var width = 960, height = 500;
-
-// var fill = d3.scale.category10();
-
-// var nodes = [
-//   {id: 1},
-//   {id: 2},
-//   {id: 3},
-//   {id: 4},
-//   {id: 5}
-// ];
-
-// var links = [
-//   {source: 1, target: 2},
-//   {source: 2, target: 3}
-// ];
-
-// var svg = d3.select("body").append("svg")
-//     .attr("width", width)
-//     .attr("height", height);
-
-// var force = d3.layout.force()
-//     .nodes(nodes)
-//     .links(links)
-//     .charge(-8*12)
-//     .gravity(0.1)
-//     .size([width, height])
-//     .on("tick", tick)
-//     .start();
-
-// svg.append('circle')
-// .attr({
-//   "class": "xos",
-//   r: 20,
-//   cx: () => width / 2,
-//   cy: () => height / 2,
-// })
-
-// var node = svg.selectAll(".node")
-//     .data(nodes)
-//   .enter().append("circle")
-//     .attr("class", "node")
-//     .attr("cx", ({ x }) => x)
-//     .attr("cy", ({ y }) => y)
-//     .attr("r", 8)
-//     .style("fill", ({}, index) => fill(index & 3))
-//     .style("stroke", ({}, index) => d3.rgb(fill(index & 3)).darker(2))
-//     .call(force.drag)
-//     .on("mousedown", ({}) => d3.event.stopPropagation());
-
-// var link = svg.selectAll(".link")
-// .data(links).enter().insert("line")
-//       .attr("class", "link");
-
-// function tick(e) {
-//   // Push different nodes in different directions for clustering.
-  
-//   node.attr("cx", function(d) { return d.x; })
-//       .attr("cy", function(d) { return d.y; });
-  
-//   link.attr("x1", function(d) { return d.source.x; })
-//       .attr("y1", function(d) { return d.source.y; })
-//       .attr("x2", function(d) { return d.target.x; })
-//       .attr("y2", function(d) { return d.target.y; });
-// }
+})();
\ No newline at end of file