[CORD-814] Rendering nodes and links for coarse tenancy graph

Change-Id: I0a72a667f5a49bb217710cd68b888a5c96ac7995
diff --git a/src/app/service-graph/components/coarse/coarse.component.html b/src/app/service-graph/components/coarse/coarse.component.html
index 75ef18b..c964808 100644
--- a/src/app/service-graph/components/coarse/coarse.component.html
+++ b/src/app/service-graph/components/coarse/coarse.component.html
@@ -1,3 +1,4 @@
 <h1>Coarse Tenancy Graph</h1>
 
-<pre>{{vm.graph | json}}</pre>
\ No newline at end of file
+<svg>
+</svg>
diff --git a/src/app/service-graph/components/coarse/coarse.component.scss b/src/app/service-graph/components/coarse/coarse.component.scss
new file mode 100644
index 0000000..f2497a9
--- /dev/null
+++ b/src/app/service-graph/components/coarse/coarse.component.scss
@@ -0,0 +1,41 @@
+@import './../../../style/vars.scss';
+@import '../../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables';
+
+xos-coarse-tenancy-graph {
+  display: block;
+  // background: $color-accent;
+
+  svg {
+    height: 400px;
+    width: 100%;
+    background-color: $panel-filled-bg;
+    border-radius: 3px;
+  }
+
+  .node-group {
+
+    .node {
+      cursor: pointer;
+    }
+
+    .node > rect {
+      stroke: $color-accent;
+      fill: $background-light-color;
+    }
+
+    .node > text {
+      fill: #fff;
+      font-size: 20px;
+    }
+  }
+
+  .link-group {
+    line {
+      stroke: $color-accent;
+    }
+  }
+  .arrow-marker {
+    stroke: $color-accent;
+    fill: $color-accent;
+  }
+}
\ No newline at end of file
diff --git a/src/app/service-graph/components/coarse/coarse.component.ts b/src/app/service-graph/components/coarse/coarse.component.ts
index b64e806..c595cf1 100644
--- a/src/app/service-graph/components/coarse/coarse.component.ts
+++ b/src/app/service-graph/components/coarse/coarse.component.ts
@@ -1,20 +1,166 @@
+import './coarse.component.scss';
+import * as d3 from 'd3';
+import * as $ from 'jquery';
 import {IXosServiceGraphStore} from '../../services/graph.store';
-import {IXosServiceGraph} from '../../interfaces';
+import {IXosServiceGraph, IXosServiceGraphNode, IXosServiceGraphLink} from '../../interfaces';
+import {XosServiceGraphConfig as config} from '../../graph.config';
+
 class XosCoarseTenancyGraphCtrl {
 
   static $inject = ['$log', 'XosServiceGraphStore'];
 
   public graph: IXosServiceGraph;
 
+  private svg;
+  private forceLayout;
+  private linkGroup;
+  private nodeGroup;
+
   constructor (
     private $log: ng.ILogService,
     private XosServiceGraphStore: IXosServiceGraphStore
   ) {
 
     this.XosServiceGraphStore.getCoarse()
-      .subscribe((res) => {
+      .subscribe((res: IXosServiceGraph) => {
+        // id there are no data, do nothing
+        if (!res.nodes || res.nodes.length === 0 || !res.links || res.links.length === 0) {
+          return;
+        }
         this.graph = res;
+        this.setupForceLayout(res);
+        this.renderNodes(res.nodes);
+        this.renderLinks(res.links);
+        this.forceLayout.start();
       });
+
+    this.handleSvg();
+  }
+
+  private getSvgDimensions(): {width: number, heigth: number} {
+    return {
+      width: $('xos-coarse-tenancy-graph svg').width(),
+      heigth: $('xos-coarse-tenancy-graph svg').height()
+    };
+  }
+
+  private handleSvg() {
+    this.svg = d3.select('svg');
+
+    this.svg.append('svg:defs')
+      .selectAll('marker')
+      .data(config.markers)
+      .enter()
+      .append('svg:marker')
+      .attr('id', d => d.id)
+      .attr('viewBox', d => d.viewBox)
+      .attr('refX', d => d.refX)
+      .attr('refY', d => d.refY)
+      .attr('markerWidth', d => d.width)
+      .attr('markerHeight', d => d.height)
+      .attr('orient', 'auto')
+      .attr('class', d => `${d.id}-marker`)
+      .append('svg:path')
+      .attr('d', d => d.path);
+
+    this.linkGroup = this.svg.append('g')
+      .attr({
+        class: 'link-group'
+      });
+
+    this.nodeGroup = this.svg.append('g')
+      .attr({
+        class: 'node-group'
+      });
+  }
+
+  private setupForceLayout(data: IXosServiceGraph) {
+
+    const tick = () => {
+      this.nodeGroup.selectAll('g.node')
+        .attr({
+          transform: d => `translate(${d.x}, ${d.y})`
+        });
+
+      this.linkGroup.selectAll('line')
+        .attr({
+          x1: l => l.source.x,
+          y1: l => l.source.y,
+          x2: l => l.target.x,
+          y2: l => l.target.y,
+        });
+    };
+    const svgDim = this.getSvgDimensions();
+    this.forceLayout = d3.layout.force()
+      .size([svgDim.width, svgDim.heigth])
+      .nodes(data.nodes)
+      .links(data.links)
+      .linkDistance(config.force.linkDistance)
+      .charge(config.force.charge)
+      .gravity(config.force.gravity)
+      .on('tick', tick);
+  }
+
+  private getSiblingTextBBox(contex: any /* D3 this */) {
+    return d3.select(contex.parentNode).select('text').node().getBBox();
+  }
+
+  private renderNodes(nodes: IXosServiceGraphNode[]) {
+    const self = this;
+    const node = this.nodeGroup
+      .selectAll('rect')
+      .data(nodes);
+
+    const entering = node.enter()
+      .append('g')
+      .attr({
+        class: 'node',
+      })
+      .call(this.forceLayout.drag)
+      .on('mousedown', () => { d3.event.stopPropagation(); })
+      .on('mouseup', (d) => { d.fixed = true; });
+
+    entering.append('rect')
+      .attr({
+        rx: config.node.radius,
+        ry: config.node.radius
+      });
+
+    entering.append('text')
+      .attr({
+        'text-anchor': 'middle'
+      })
+      .text(n => n.label);
+
+    const existing = node.selectAll('rect');
+
+
+    // resize node > rect as contained text
+    existing.each(function() {
+      const textBBox = self.getSiblingTextBBox(this);
+      const rect = d3.select(this);
+      rect.attr({
+        width: textBBox.width + config.node.padding,
+        height: textBBox.height + config.node.padding,
+        x: textBBox.x - (config.node.padding / 2),
+        y: textBBox.y - (config.node.padding / 2)
+      });
+    });
+  }
+
+  private renderLinks(links: IXosServiceGraphLink[]) {
+    const link = this.linkGroup
+      .selectAll('rect')
+      .data(links);
+
+    const entering = link.enter()
+      .append('g')
+      .attr({
+        class: 'link',
+      });
+
+    entering.append('line')
+      .attr('marker-start', 'url(#arrow)');
   }
 }