blob: b0f71a66dc9a69523272c4815009600d0965f808 [file] [log] [blame]
Matteo Scandolo75171782017-03-08 14:17:01 -08001import {IXosServiceGraphStore} from '../../services/graph.store';
2import './fine-grained.component.scss';
3import * as d3 from 'd3';
4import * as $ from 'jquery';
5import {Subscription} from 'rxjs';
6import {XosServiceGraphConfig as config} from '../../graph.config';
7import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
8import {IXosServiceGraph, IXosServiceGraphLink, IXosServiceGraphNode} from '../../interfaces';
9
10class XosFineGrainedTenancyGraphCtrl {
11 static $inject = [
12 '$log',
13 'XosServiceGraphStore',
14 'XosDebouncer'
15 ];
16
17 public graph: IXosServiceGraph;
18
19 private GraphSubscription: Subscription;
20 private svg;
21 private forceLayout;
22 private linkGroup;
23 private nodeGroup;
24
25 // debounced functions
26 private renderGraph;
27
28 constructor(
29 private $log: ng.ILogService,
30 private XosServiceGraphStore: IXosServiceGraphStore,
31 private XosDebouncer: IXosDebouncer
32 ) {
33 this.handleSvg();
34 this.setupForceLayout();
35 this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 500, this);
36
37 $(window).on('resize', () => {
38 this.setupForceLayout();
39 this.renderGraph();
40 });
41
42 this.GraphSubscription = this.XosServiceGraphStore.get()
43 .subscribe(
44 (graph) => {
45
46 if (!graph.nodes || graph.nodes.length === 0 || !graph.links || graph.links.length === 0) {
47 return;
48 }
49
50 this.$log.debug(`[XosFineGrainedTenancyGraphCtrl] Coarse Event and render`, graph);
51 this.graph = graph;
52 this.renderGraph();
53 },
54 (err) => {
55 this.$log.error(`[XosFineGrainedTenancyGraphCtrl] Error: `, err);
56 }
57 );
58 }
59
60 $onDestroy() {
61 this.GraphSubscription.unsubscribe();
62 }
63
64 private _renderGraph() {
65 this.addNodeLinksToForceLayout(this.graph);
66 this.renderNodes(this.graph.nodes);
67 this.renderLinks(this.graph.links);
68 }
69
70 private getSvgDimensions(): {width: number, heigth: number} {
71 return {
72 width: $('xos-fine-grained-tenancy-graph svg').width(),
73 heigth: $('xos-fine-grained-tenancy-graph svg').height()
74 };
75 }
76
77 private handleSvg() {
78 this.svg = d3.select('svg');
79
80 this.linkGroup = this.svg.append('g')
81 .attr({
82 class: 'link-group'
83 });
84
85 this.nodeGroup = this.svg.append('g')
86 .attr({
87 class: 'node-group'
88 });
89 }
90
91 private setupForceLayout() {
92
93 const tick = () => {
94 this.nodeGroup.selectAll('g.node')
95 .attr({
96 transform: d => `translate(${d.x}, ${d.y})`
97 });
98
99 this.linkGroup.selectAll('line')
100 .attr({
101 x1: l => l.source.x || 0,
102 y1: l => l.source.y || 0,
103 x2: l => l.target.x || 0,
104 y2: l => l.target.y || 0,
105 });
106 };
107 const svgDim = this.getSvgDimensions();
108 this.forceLayout = d3.layout.force()
109 .size([svgDim.width, svgDim.heigth])
110 .linkDistance(config.force.linkDistance)
111 .charge(config.force.charge)
112 .gravity(config.force.gravity)
113 .on('tick', tick);
114 }
115
116 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
117 this.forceLayout
118 .nodes(data.nodes)
119 .links(data.links)
120 .start();
121 }
122
123 private getSiblingTextBBox(contex: any /* D3 this */) {
124 return d3.select(contex.parentNode).select('text').node().getBBox();
125 }
126
127 private renderServiceNodes(nodes: any) {
128
129 const self = this;
130 nodes.append('rect')
131 .attr({
132 rx: config.node.radius,
133 ry: config.node.radius
134 });
135
136 nodes.append('text')
137 .attr({
138 'text-anchor': 'middle'
139 })
140 .text(n => n.label);
141 // .text(n => `${n.id} - ${n.label}`);
142
143 const existing = nodes.selectAll('rect');
144
145 // resize node > rect as contained text
146 existing.each(function() {
147 const textBBox = self.getSiblingTextBBox(this);
148 const rect = d3.select(this);
149 rect.attr({
150 width: textBBox.width + config.node.padding,
151 height: textBBox.height + config.node.padding,
152 x: textBBox.x - (config.node.padding / 2),
153 y: textBBox.y - (config.node.padding / 2)
154 });
155 });
156 }
157
158 private renderTenantNodes(nodes: any) {
159 nodes.append('rect')
160 .attr({
161 width: 40,
162 height: 40,
163 x: -25,
164 y: -25,
165 transform: `rotate(45)`
166 });
167
168 nodes.append('text')
169 .attr({
170 'text-anchor': 'middle'
171 })
172 .text(n => n.label);
173 }
174
175 private renderNetworkNodes(nodes: any) {
176 const self = this;
177 nodes.append('circle');
178
179 nodes.append('text')
180 .attr({
181 'text-anchor': 'middle'
182 })
183 .text(n => n.label);
184
185 const existing = nodes.selectAll('circle');
186
187 // resize node > rect as contained text
188 existing.each(function() {
189 const textBBox = self.getSiblingTextBBox(this);
190 const rect = d3.select(this);
191 rect.attr({
192 r: (textBBox.width / 2) + config.node.padding,
193 cy: - (textBBox.height / 4)
194 });
195 });
196 }
197
198 private renderSubscriberNodes(nodes: any) {
199 const self = this;
200 nodes.append('rect');
201
202 nodes.append('text')
203 .attr({
204 'text-anchor': 'middle'
205 })
206 .text(n => n.label);
207
208 const existing = nodes.selectAll('rect');
209
210 // resize node > rect as contained text
211 existing.each(function() {
212 const textBBox = self.getSiblingTextBBox(this);
213 const rect = d3.select(this);
214 rect.attr({
215 width: textBBox.width + config.node.padding,
216 height: textBBox.height + config.node.padding,
217 x: textBBox.x - (config.node.padding / 2),
218 y: textBBox.y - (config.node.padding / 2)
219 });
220 });
221 }
222
223 private renderNodes(nodes: IXosServiceGraphNode[]) {
224 const node = this.nodeGroup
225 .selectAll('g.node')
226 .data(nodes, n => n.id);
227
228 const svgDim = this.getSvgDimensions();
229 const hStep = svgDim.width / (nodes.length - 1);
230 const vStep = svgDim.heigth / (nodes.length - 1);
231 const entering = node.enter()
232 .append('g')
233 .attr({
234 class: n => `node ${n.type}`,
235 transform: (n, i) => `translate(${hStep * i}, ${vStep * i})`
236 })
237 .call(this.forceLayout.drag)
238 .on('mousedown', () => {
239 d3.event.stopPropagation();
240 })
241 .on('mouseup', (d) => {
242 d.fixed = true;
243 });
244
245 this.renderServiceNodes(entering.filter('.service'));
246 this.renderTenantNodes(entering.filter('.tenant'));
247 this.renderNetworkNodes(entering.filter('.network'));
248 this.renderSubscriberNodes(entering.filter('.subscriber'));
249 }
250
251 private renderLinks(links: IXosServiceGraphLink[]) {
252 const link = this.linkGroup
253 .selectAll('line')
254 .data(links, l => l.id);
255
256 const entering = link.enter();
257
258 entering.append('line')
259 .attr({
260 class: 'link',
261 'marker-start': 'url(#arrow)'
262 });
263 }
264}
265
266export const XosFineGrainedTenancyGraph: angular.IComponentOptions = {
267 template: require('./fine-grained.component.html'),
268 controllerAs: 'vm',
269 controller: XosFineGrainedTenancyGraphCtrl,
270};