[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,
+};