[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)');
}
}