blob: d610aa6ea97f10f3362d3078f6c73121f9428bfd [file] [log] [blame]
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -08001import './coarse.component.scss';
2import * as d3 from 'd3';
3import * as $ from 'jquery';
Matteo Scandolo968e7f22017-03-03 11:49:18 -08004import {IXosServiceGraphStore} from '../../services/graph.store';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -08005import {IXosServiceGraph, IXosServiceGraphNode, IXosServiceGraphLink} from '../../interfaces';
6import {XosServiceGraphConfig as config} from '../../graph.config';
Matteo Scandoloa160eef2017-03-06 17:21:26 -08007import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
8import {Subscription} from 'rxjs';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -08009
Matteo Scandolo968e7f22017-03-03 11:49:18 -080010class XosCoarseTenancyGraphCtrl {
11
Matteo Scandoloa160eef2017-03-06 17:21:26 -080012 static $inject = [
13 '$log',
14 'XosServiceGraphStore',
15 'XosDebouncer'
16 ];
Matteo Scandolo968e7f22017-03-03 11:49:18 -080017
18 public graph: IXosServiceGraph;
19
Matteo Scandoloa160eef2017-03-06 17:21:26 -080020 private CoarseGraphSubscription: Subscription;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080021 private svg;
22 private forceLayout;
23 private linkGroup;
24 private nodeGroup;
25
Matteo Scandolo75171782017-03-08 14:17:01 -080026 // debounced functions
Matteo Scandoloa160eef2017-03-06 17:21:26 -080027 private renderGraph;
28
Matteo Scandolo968e7f22017-03-03 11:49:18 -080029 constructor (
30 private $log: ng.ILogService,
Matteo Scandoloa160eef2017-03-06 17:21:26 -080031 private XosServiceGraphStore: IXosServiceGraphStore,
32 private XosDebouncer: IXosDebouncer
Matteo Scandolo968e7f22017-03-03 11:49:18 -080033 ) {
34
Matteo Scandoloa160eef2017-03-06 17:21:26 -080035 }
36
37 $onInit() {
38 this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 500, this);
39
40 this.CoarseGraphSubscription = this.XosServiceGraphStore.getCoarse()
41 .subscribe(
42 (res: IXosServiceGraph) => {
Matteo Scandolo75171782017-03-08 14:17:01 -080043
Matteo Scandoloa160eef2017-03-06 17:21:26 -080044 // id there are no data, do nothing
45 if (!res.nodes || res.nodes.length === 0 || !res.links || res.links.length === 0) {
46 return;
47 }
48 this.$log.debug(`[XosCoarseTenancyGraph] Coarse Event and render`, res);
49 this.graph = res;
50 this.renderGraph();
51 },
52 err => {
53 this.$log.error(`[XosCoarseTenancyGraph] Coarse Event error`, err);
54 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080055
56 this.handleSvg();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080057 this.setupForceLayout();
58
59 $(window).on('resize', () => {
60 this.setupForceLayout();
61 this.renderGraph();
62 });
63 }
64
65 $onDestroy() {
66 this.CoarseGraphSubscription.unsubscribe();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080067 }
68
69 private _renderGraph() {
70 this.addNodeLinksToForceLayout(this.graph);
71 this.renderNodes(this.graph.nodes);
72 this.renderLinks(this.graph.links);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080073 }
74
75 private getSvgDimensions(): {width: number, heigth: number} {
76 return {
77 width: $('xos-coarse-tenancy-graph svg').width(),
78 heigth: $('xos-coarse-tenancy-graph svg').height()
79 };
80 }
81
82 private handleSvg() {
83 this.svg = d3.select('svg');
84
85 this.svg.append('svg:defs')
86 .selectAll('marker')
87 .data(config.markers)
88 .enter()
89 .append('svg:marker')
90 .attr('id', d => d.id)
91 .attr('viewBox', d => d.viewBox)
92 .attr('refX', d => d.refX)
93 .attr('refY', d => d.refY)
94 .attr('markerWidth', d => d.width)
95 .attr('markerHeight', d => d.height)
96 .attr('orient', 'auto')
97 .attr('class', d => `${d.id}-marker`)
98 .append('svg:path')
99 .attr('d', d => d.path);
100
101 this.linkGroup = this.svg.append('g')
102 .attr({
103 class: 'link-group'
104 });
105
106 this.nodeGroup = this.svg.append('g')
107 .attr({
108 class: 'node-group'
109 });
110 }
111
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800112 private setupForceLayout() {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800113
114 const tick = () => {
115 this.nodeGroup.selectAll('g.node')
116 .attr({
117 transform: d => `translate(${d.x}, ${d.y})`
118 });
119
120 this.linkGroup.selectAll('line')
121 .attr({
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800122 x1: l => l.source.x || 0,
123 y1: l => l.source.y || 0,
124 x2: l => l.target.x || 0,
125 y2: l => l.target.y || 0,
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800126 });
127 };
128 const svgDim = this.getSvgDimensions();
129 this.forceLayout = d3.layout.force()
130 .size([svgDim.width, svgDim.heigth])
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800131 .linkDistance(config.force.linkDistance)
132 .charge(config.force.charge)
133 .gravity(config.force.gravity)
134 .on('tick', tick);
135 }
136
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800137 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
138 this.forceLayout
139 .nodes(data.nodes)
140 .links(data.links)
141 .start();
142 }
143
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800144 private getSiblingTextBBox(contex: any /* D3 this */) {
145 return d3.select(contex.parentNode).select('text').node().getBBox();
146 }
147
148 private renderNodes(nodes: IXosServiceGraphNode[]) {
149 const self = this;
150 const node = this.nodeGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800151 .selectAll('g.node')
152 .data(nodes, n => n.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800153
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800154 const svgDim = this.getSvgDimensions();
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800155 const entering = node.enter()
156 .append('g')
157 .attr({
158 class: 'node',
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800159 transform: `translate(${svgDim.width / 2}, ${svgDim.heigth / 2})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800160 })
161 .call(this.forceLayout.drag)
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800162 .on('mousedown', () => {
163 d3.event.stopPropagation();
164 })
165 .on('mouseup', (d) => {
166 d.fixed = true;
167 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800168
169 entering.append('rect')
170 .attr({
171 rx: config.node.radius,
172 ry: config.node.radius
173 });
174
175 entering.append('text')
176 .attr({
177 'text-anchor': 'middle'
178 })
179 .text(n => n.label);
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800180 // .text(n => `${n.id} - ${n.label}`);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800181
182 const existing = node.selectAll('rect');
183
184
185 // resize node > rect as contained text
186 existing.each(function() {
187 const textBBox = self.getSiblingTextBBox(this);
188 const rect = d3.select(this);
189 rect.attr({
190 width: textBBox.width + config.node.padding,
191 height: textBBox.height + config.node.padding,
192 x: textBBox.x - (config.node.padding / 2),
193 y: textBBox.y - (config.node.padding / 2)
194 });
195 });
196 }
197
198 private renderLinks(links: IXosServiceGraphLink[]) {
199 const link = this.linkGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800200 .selectAll('line')
201 .data(links, l => l.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800202
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800203 const entering = link.enter();
204 // NOTE do we need to have groups?
205 // .append('g')
206 // .attr({
207 // class: 'link',
208 // });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800209
210 entering.append('line')
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800211 .attr({
212 class: 'link',
213 'marker-start': 'url(#arrow)'
214 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800215 }
216}
217
218export const XosCoarseTenancyGraph: angular.IComponentOptions = {
219 template: require('./coarse.component.html'),
220 controllerAs: 'vm',
221 controller: XosCoarseTenancyGraphCtrl,
222};