Unsubscribing from observable on CoarseGraph Component destroy

Change-Id: I99c59e9c8355edccaca108534d14a1e0901bcc45
diff --git a/src/app/service-graph/components/coarse/coarse.component.scss b/src/app/service-graph/components/coarse/coarse.component.scss
index f2497a9..1cf8f3c 100644
--- a/src/app/service-graph/components/coarse/coarse.component.scss
+++ b/src/app/service-graph/components/coarse/coarse.component.scss
@@ -20,7 +20,7 @@
 
     .node > rect {
       stroke: $color-accent;
-      fill: $background-light-color;
+      fill: $background-color;
     }
 
     .node > text {
diff --git a/src/app/service-graph/components/coarse/coarse.component.ts b/src/app/service-graph/components/coarse/coarse.component.ts
index c595cf1..566698c 100644
--- a/src/app/service-graph/components/coarse/coarse.component.ts
+++ b/src/app/service-graph/components/coarse/coarse.component.ts
@@ -4,37 +4,72 @@
 import {IXosServiceGraphStore} from '../../services/graph.store';
 import {IXosServiceGraph, IXosServiceGraphNode, IXosServiceGraphLink} from '../../interfaces';
 import {XosServiceGraphConfig as config} from '../../graph.config';
+import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
+import {Subscription} from 'rxjs';
 
 class XosCoarseTenancyGraphCtrl {
 
-  static $inject = ['$log', 'XosServiceGraphStore'];
+  static $inject = [
+    '$log',
+    'XosServiceGraphStore',
+    'XosDebouncer'
+  ];
 
   public graph: IXosServiceGraph;
 
+  private CoarseGraphSubscription: Subscription;
   private svg;
   private forceLayout;
   private linkGroup;
   private nodeGroup;
 
+  // debounce functions
+  private renderGraph;
+
   constructor (
     private $log: ng.ILogService,
-    private XosServiceGraphStore: IXosServiceGraphStore
+    private XosServiceGraphStore: IXosServiceGraphStore,
+    private XosDebouncer: IXosDebouncer
   ) {
 
-    this.XosServiceGraphStore.getCoarse()
-      .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();
-      });
+  }
+
+  $onInit() {
+    this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 500, this);
+
+    this.CoarseGraphSubscription = this.XosServiceGraphStore.getCoarse()
+      .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.$log.debug(`[XosCoarseTenancyGraph] Coarse Event and render`, res);
+          this.graph = res;
+          this.renderGraph();
+        },
+        err => {
+          this.$log.error(`[XosCoarseTenancyGraph] Coarse Event error`, err);
+        });
 
     this.handleSvg();
+    this.setupForceLayout();
+
+    $(window).on('resize', () => {
+      this.setupForceLayout();
+      this.renderGraph();
+    });
+  }
+
+  $onDestroy() {
+    this.CoarseGraphSubscription.unsubscribe();
+    this.XosServiceGraphStore.dispose();
+  }
+
+  private _renderGraph() {
+    this.addNodeLinksToForceLayout(this.graph);
+    this.renderNodes(this.graph.nodes);
+    this.renderLinks(this.graph.links);
   }
 
   private getSvgDimensions(): {width: number, heigth: number} {
@@ -74,7 +109,7 @@
       });
   }
 
-  private setupForceLayout(data: IXosServiceGraph) {
+  private setupForceLayout() {
 
     const tick = () => {
       this.nodeGroup.selectAll('g.node')
@@ -84,23 +119,28 @@
 
       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,
+          x1: l => l.source.x || 0,
+          y1: l => l.source.y || 0,
+          x2: l => l.target.x || 0,
+          y2: l => l.target.y || 0,
         });
     };
     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 addNodeLinksToForceLayout(data: IXosServiceGraph) {
+    this.forceLayout
+      .nodes(data.nodes)
+      .links(data.links)
+      .start();
+  }
+
   private getSiblingTextBBox(contex: any /* D3 this */) {
     return d3.select(contex.parentNode).select('text').node().getBBox();
   }
@@ -108,17 +148,23 @@
   private renderNodes(nodes: IXosServiceGraphNode[]) {
     const self = this;
     const node = this.nodeGroup
-      .selectAll('rect')
-      .data(nodes);
+      .selectAll('g.node')
+      .data(nodes, n => n.id);
 
+    const svgDim = this.getSvgDimensions();
     const entering = node.enter()
       .append('g')
       .attr({
         class: 'node',
+        transform: `translate(${svgDim.width / 2}, ${svgDim.heigth / 2})`
       })
       .call(this.forceLayout.drag)
-      .on('mousedown', () => { d3.event.stopPropagation(); })
-      .on('mouseup', (d) => { d.fixed = true; });
+      .on('mousedown', () => {
+        d3.event.stopPropagation();
+      })
+      .on('mouseup', (d) => {
+        d.fixed = true;
+      });
 
     entering.append('rect')
       .attr({
@@ -131,6 +177,7 @@
         'text-anchor': 'middle'
       })
       .text(n => n.label);
+      // .text(n => `${n.id} - ${n.label}`);
 
     const existing = node.selectAll('rect');
 
@@ -150,17 +197,21 @@
 
   private renderLinks(links: IXosServiceGraphLink[]) {
     const link = this.linkGroup
-      .selectAll('rect')
-      .data(links);
+      .selectAll('line')
+      .data(links, l => l.id);
 
-    const entering = link.enter()
-      .append('g')
-      .attr({
-        class: 'link',
-      });
+    const entering = link.enter();
+      // NOTE do we need to have groups?
+      // .append('g')
+      // .attr({
+      //   class: 'link',
+      // });
 
     entering.append('line')
-      .attr('marker-start', 'url(#arrow)');
+      .attr({
+        class: 'link',
+        'marker-start': 'url(#arrow)'
+      });
   }
 }
 
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 9636bb2..821da41 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -1,5 +1,5 @@
 import * as _ from 'lodash';
-import {Observable, BehaviorSubject} from 'rxjs';
+import {Observable, BehaviorSubject, Subscription} from 'rxjs';
 import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {
   IXosServiceGraph, IXosServiceModel, IXosTenantModel, IXosCoarseGraphData,
@@ -9,6 +9,7 @@
 export interface IXosServiceGraphStore {
   get(): Observable<IXosServiceGraph>;
   getCoarse(): Observable<IXosServiceGraph>;
+  dispose(): void;
 }
 
 export class XosServiceGraphStore implements IXosServiceGraphStore {
@@ -36,8 +37,8 @@
   private handleData;
 
   // datastore
-  private ServiceObservable: Observable<any>;
-  private TenantObservable: Observable<any>;
+  private ServiceSubscription: Subscription;
+  private TenantSubscription: Subscription;
 
   constructor (
     private $log: ng.ILogService,
@@ -50,27 +51,25 @@
     // we want to have a quiet period of 500ms from the last event before doing anything
     this.handleData = this.XosDebouncer.debounce(this._handleData, 500, this, false);
 
-    this.ServiceObservable = this.XosModelStore.query('Service', '/core/services');
-    this.TenantObservable = this.XosModelStore.query('Tenant', '/core/tenants');
 
     // observe models and populate graphData
-    this.ServiceObservable
+    this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
       .subscribe(
         (res) => {
           this.combineData(res, 'services');
         },
         (err) => {
-          this.$log.error(err);
+          this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
         }
       );
 
-    this.TenantObservable
+    this.TenantSubscription = this.XosModelStore.query('Tenant', '/core/tenants')
       .subscribe(
         (res) => {
           this.combineData(res, 'tenants');
         },
         (err) => {
-          this.$log.error(err);
+          this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
         }
       );
 
@@ -91,6 +90,12 @@
       );
   }
 
+  public dispose() {
+    // cancel subscriptions from observables
+    this.ServiceSubscription.unsubscribe();
+    this.TenantSubscription.unsubscribe();
+  }
+
   public get() {
     return this.d3FineGrainedGraph.asObservable();
   }
@@ -118,20 +123,19 @@
     });
   }
 
-  private getNodeIndexById(id: number, nodes: IXosServiceModel[]) {
+  private getCoarseNodeIndexById(id: number, nodes: IXosServiceModel[]) {
     return _.findIndex(nodes, {id: id});
   }
 
   private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
-
     // TODO find how to bind source/target by node ID and not by position in array (ask Simon?)
     const links: IXosServiceGraphLink[] = _.chain(data.tenants)
       .filter((t: IXosTenantModel) => t.kind === 'coarse')
       .map((t: IXosTenantModel) => {
         return {
           id: t.id,
-          source: this.getNodeIndexById(t.provider_service_id, data.services),
-          target: this.getNodeIndexById(t.subscriber_service_id, data.services),
+          source: this.getCoarseNodeIndexById(t.provider_service_id, data.services),
+          target: this.getCoarseNodeIndexById(t.subscriber_service_id, data.services),
           model: t
         };
       })