[CORD-1943] New service graph
- labels
- enforcing service position
- started documentation
- toggling service instances
- toggle fullscreen

Change-Id: I01b71fb2607fb58711d4624f6b5b6479609b2f4f
diff --git a/src/app/service-graph/components/graph/graph.component.ts b/src/app/service-graph/components/graph/graph.component.ts
new file mode 100644
index 0000000..5769b6f
--- /dev/null
+++ b/src/app/service-graph/components/graph/graph.component.ts
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './graph.component.scss';
+
+import * as d3 from 'd3';
+import * as $ from 'jquery';
+
+import {IXosGraphStore} from '../../services/graph.store';
+import {Subscription} from 'rxjs/Subscription';
+import {XosServiceGraphConfig as config} from '../../graph.config';
+import {IXosGraphHelpers} from '../../services/d3-helpers/graph-elements.helpers';
+import {IXosServiceGraphIcons} from '../../services/d3-helpers/graph-icons.service';
+import {IXosNodePositioner} from '../../services/node-positioner.service';
+import {IXosNodeRenderer} from '../../services/renderer/node.renderer';
+import {IXosSgNode} from '../../interfaces';
+import {IXosGraphConfig} from '../../services/graph.config';
+
+class XosServiceGraphCtrl {
+  static $inject = [
+    '$log',
+    '$scope',
+    'XosGraphStore',
+    'XosGraphHelpers',
+    'XosServiceGraphIcons',
+    'XosNodePositioner',
+    'XosNodeRenderer',
+    'XosGraphConfig'
+  ];
+
+  public loader: boolean = true;
+
+  private GraphSubscription: Subscription;
+  private graph: any; // this is the Graph instance
+
+  // graph element
+  private svg;
+  private linkGroup;
+  private nodeGroup;
+  private forceLayout;
+
+  constructor (
+    private $log: ng.ILogService,
+    private $scope: ng.IScope,
+    private XosGraphStore: IXosGraphStore,
+    private XosGraphHelpers: IXosGraphHelpers,
+    private XosServiceGraphIcons: IXosServiceGraphIcons,
+    private XosNodePositioner: IXosNodePositioner,
+    private XosNodeRenderer: IXosNodeRenderer,
+    private XosGraphConfig: IXosGraphConfig
+  ) {
+    this.$log.info('[XosServiceGraph] Component setup');
+
+    this.XosGraphConfig.setupKeyboardShortcuts();
+
+    this.setupSvg();
+    this.setupForceLayout();
+
+    this.GraphSubscription = this.XosGraphStore.get()
+      .subscribe(
+        graph => {
+          this.graph = graph;
+          if (this.graph.nodes().length > 0) {
+            this.loader = false;
+            this.renderGraph(this.graph);
+          }
+        },
+        error => {
+          this.$log.error('[XosServiceGraph] XosGraphStore observable error: ', error);
+        }
+      );
+
+    this.$scope.$on('xos.sg.update', () => {
+      this.$log.info(`[XosServiceGraph] Received event: xos.sg.update`);
+      this.renderGraph(this.graph);
+    });
+  }
+
+  $onDestroy() {
+    this.GraphSubscription.unsubscribe();
+  }
+
+  public closeFullscreen() {
+    this.XosGraphConfig.toggleFullscreen();
+  }
+
+  private setupSvg() {
+    this.svg = d3.select('xos-service-graph svg');
+
+    this.linkGroup = this.svg.append('g')
+      .attr({
+        class: 'link-group'
+      });
+
+    this.nodeGroup = this.svg.append('g')
+      .attr({
+        class: 'node-group'
+      });
+  }
+
+  private setupForceLayout() {
+    this.$log.debug(`[XosServiceGraph] Setup Force Layout`);
+    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 || 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.height])
+      .on('tick', tick);
+  }
+
+  private getSvgDimensions(): {width: number, height: number} {
+    return {
+      width: $('xos-service-graph svg').width(),
+      height: $('xos-service-graph svg').height()
+    };
+  }
+
+  private renderGraph(graph: any) {
+    let nodes: IXosSgNode[] = this.XosGraphStore.nodesFromGraph(graph);
+    let links = this.XosGraphStore.linksFromGraph(graph);
+    const svgDim = this.getSvgDimensions();
+
+    this.XosNodePositioner.positionNodes(svgDim, nodes)
+      .then((nodes: IXosSgNode[]) => {
+
+        this.forceLayout
+          .nodes(nodes)
+          .links(links)
+          .size([svgDim.width, svgDim.height])
+          .linkDistance(config.force.linkDistance)
+          .charge(config.force.charge)
+          .gravity(config.force.gravity)
+          .start();
+
+        // render nodes
+        this.XosNodeRenderer.renderNodes(this.forceLayout, this.nodeGroup, nodes);
+        this.renderLinks(links);
+      });
+  }
+
+  private renderLinks(links: any[]) {
+
+    const link = this.linkGroup
+      .selectAll('line')
+      .data(links, l => l.id);
+
+    const entering = link.enter();
+
+    entering.append('line')
+      .attr({
+        id: n => n.id,
+        class: n => n.type
+      });
+
+    link.exit().remove();
+  }
+
+}
+
+export const XosServiceGraph: angular.IComponentOptions = {
+  template: require('./graph.component.html'),
+  controllerAs: 'vm',
+  controller: XosServiceGraphCtrl,
+};