blob: 6d57aadc2abb8c7b397d4aea7254b6267f23deff [file] [log] [blame]
Matteo Scandolo75171782017-03-08 14:17:01 -08001import './fine-grained.component.scss';
2import * as d3 from 'd3';
3import * as $ from 'jquery';
Matteo Scandolobafd8d62017-03-29 23:23:00 -07004import * as _ from 'lodash';
Matteo Scandolo75171782017-03-08 14:17:01 -08005import {Subscription} from 'rxjs';
6import {XosServiceGraphConfig as config} from '../../graph.config';
7import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
8import {IXosServiceGraph, IXosServiceGraphLink, IXosServiceGraphNode} from '../../interfaces';
Matteo Scandolo520a8a12017-03-10 17:31:37 -08009import {IXosModelDiscovererService} from '../../../datasources/helpers/model-discoverer.service';
10import {IXosSidePanelService} from '../../../core/side-panel/side-panel.service';
Matteo Scandolo7629cc42017-03-13 14:12:15 -070011import {IXosGraphHelpers} from '../../services/d3-helpers/graph.helpers';
Matteo Scandolobafd8d62017-03-29 23:23:00 -070012import {IXosServiceGraphExtender, IXosServiceGraphReducer} from '../../services/graph.extender';
Matteo Scandolo72181592017-07-25 14:49:40 -070013import {IXosServiceInstanceGraphStore} from '../../services/service-instance.graph.store';
Matteo Scandolo75171782017-03-08 14:17:01 -080014
15class XosFineGrainedTenancyGraphCtrl {
16 static $inject = [
17 '$log',
Matteo Scandolo72181592017-07-25 14:49:40 -070018 'XosServiceInstanceGraphStore',
Matteo Scandolo520a8a12017-03-10 17:31:37 -080019 'XosDebouncer',
20 'XosModelDiscoverer',
Matteo Scandolo7629cc42017-03-13 14:12:15 -070021 'XosSidePanel',
Matteo Scandolobafd8d62017-03-29 23:23:00 -070022 'XosGraphHelpers',
23 'XosServiceGraphExtender'
Matteo Scandolo75171782017-03-08 14:17:01 -080024 ];
25
26 public graph: IXosServiceGraph;
27
28 private GraphSubscription: Subscription;
29 private svg;
30 private forceLayout;
31 private linkGroup;
32 private nodeGroup;
Simon Huntc8f23142017-03-14 14:11:13 -070033 private defs;
Matteo Scandolo6a7435f2017-03-24 18:07:17 -070034 private textSize = 20;
35 private textOffset = this.textSize / 4;
Matteo Scandolo75171782017-03-08 14:17:01 -080036
37 // debounced functions
38 private renderGraph;
39
40 constructor(
41 private $log: ng.ILogService,
Matteo Scandolo72181592017-07-25 14:49:40 -070042 private XosServiceInstanceGraphStore: IXosServiceInstanceGraphStore,
Matteo Scandolo520a8a12017-03-10 17:31:37 -080043 private XosDebouncer: IXosDebouncer,
44 private XosModelDiscoverer: IXosModelDiscovererService,
Matteo Scandolo7629cc42017-03-13 14:12:15 -070045 private XosSidePanel: IXosSidePanelService,
Matteo Scandolobafd8d62017-03-29 23:23:00 -070046 private XosGraphHelpers: IXosGraphHelpers,
47 private XosServiceGraphExtender: IXosServiceGraphExtender
Matteo Scandolo75171782017-03-08 14:17:01 -080048 ) {
49 this.handleSvg();
Simon Huntc8f23142017-03-14 14:11:13 -070050 this.loadDefs();
Matteo Scandolo75171782017-03-08 14:17:01 -080051 this.setupForceLayout();
Matteo Scandolobafd8d62017-03-29 23:23:00 -070052 this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 1000, this);
Matteo Scandolo75171782017-03-08 14:17:01 -080053
54 $(window).on('resize', () => {
55 this.setupForceLayout();
56 this.renderGraph();
57 });
58
Matteo Scandolo72181592017-07-25 14:49:40 -070059 this.GraphSubscription = this.XosServiceInstanceGraphStore.get()
Matteo Scandolo75171782017-03-08 14:17:01 -080060 .subscribe(
61 (graph) => {
Matteo Scandolo72181592017-07-25 14:49:40 -070062 this.$log.debug(`[XosServiceInstanceGraphStore] Fine-Grained Event and render`, graph);
Matteo Scandolo75171782017-03-08 14:17:01 -080063
Matteo Scandolo265c2042017-03-20 10:15:40 -070064 if (!graph || !graph.nodes || !graph.links) {
Matteo Scandolo75171782017-03-08 14:17:01 -080065 return;
66 }
67
Matteo Scandolobafd8d62017-03-29 23:23:00 -070068 _.forEach(this.XosServiceGraphExtender.getFinegrained(), (r: IXosServiceGraphReducer) => {
69 graph = r.reducer(graph);
70 });
71
Matteo Scandolo75171782017-03-08 14:17:01 -080072 this.graph = graph;
73 this.renderGraph();
74 },
75 (err) => {
76 this.$log.error(`[XosFineGrainedTenancyGraphCtrl] Error: `, err);
77 }
78 );
79 }
80
81 $onDestroy() {
82 this.GraphSubscription.unsubscribe();
83 }
84
85 private _renderGraph() {
Matteo Scandolo9b460042017-04-14 16:24:45 -070086 if (!angular.isDefined(this.graph) || !angular.isDefined(this.graph.nodes) || !angular.isDefined(this.graph.links)) {
87 return;
88 }
Matteo Scandolo75171782017-03-08 14:17:01 -080089 this.addNodeLinksToForceLayout(this.graph);
90 this.renderNodes(this.graph.nodes);
91 this.renderLinks(this.graph.links);
92 }
93
Max Chu2bfddde2017-06-29 13:41:52 -070094 private getSvgDimensions(): {width: number, height: number} {
Matteo Scandolo75171782017-03-08 14:17:01 -080095 return {
96 width: $('xos-fine-grained-tenancy-graph svg').width(),
Max Chu2bfddde2017-06-29 13:41:52 -070097 height: $('xos-fine-grained-tenancy-graph svg').height()
Matteo Scandolo75171782017-03-08 14:17:01 -080098 };
99 }
100
101 private handleSvg() {
102 this.svg = d3.select('svg');
103
Simon Huntc8f23142017-03-14 14:11:13 -0700104 this.defs = this.svg.append('defs');
105
Matteo Scandolo75171782017-03-08 14:17:01 -0800106 this.linkGroup = this.svg.append('g')
107 .attr({
108 class: 'link-group'
109 });
110
111 this.nodeGroup = this.svg.append('g')
112 .attr({
113 class: 'node-group'
114 });
115 }
116
Simon Huntc8f23142017-03-14 14:11:13 -0700117 private loadDefs() {
118 const cloud = {
119 vbox: '0 0 303.8 185.8',
120 path: `M88.6,44.3c31.7-45.5,102.1-66.7,135-3
121 M37.8,142.9c-22.5,3.5-60.3-32.4-16.3-64.2
122 M101.8,154.2c-15.6,59.7-121.4,18.8-77.3-13
123 M194.6,150c-35.4,51.8-85.7,34.3-98.8-9.5
124 M274.4,116.4c29.4,73.2-81.9,80.3-87.7,44.3
125 M28.5,89.2C3.7,77.4,55.5,4.8,95.3,36.1
126 M216.1,28.9C270.9-13,340.8,91,278.4,131.1`,
127 bgpath: `M22,78.3C21.5,55.1,62.3,10.2,95.2,36
128 h0c31.9-33.4,88.1-50.5,120.6-7.2l0.3,0.2
129 C270.9-13,340.8,91,278.4,131.1v-0.5
130 c10.5,59.8-86.4,63.7-91.8,30.1h-0.4
131 c-30.2,33.6-67.6,24-84.6-6v-0.4
132 c-15.6,59.7-121.4,18.8-77.3-13
133 l-0.2-.2c-20.2-7.9-38.6-36.5-2.8-62.3Z`
134 };
135
136 this.defs.append('symbol')
137 .attr({ id: 'cloud', viewBox: cloud.vbox })
138 .append('path').attr('d', cloud.path);
139
140 this.defs.append('symbol')
141 .attr({ id: 'cloud_bg', viewBox: cloud.vbox })
142 .append('path').attr('d', cloud.bgpath);
143 }
144
Matteo Scandolo75171782017-03-08 14:17:01 -0800145 private setupForceLayout() {
Matteo Scandolo0e8a8422017-03-25 14:55:40 -0700146 this.$log.debug(`[XosFineGrainedTenancyGraphCtrl] Setup Force Layout`);
Matteo Scandolo75171782017-03-08 14:17:01 -0800147 const tick = () => {
148 this.nodeGroup.selectAll('g.node')
149 .attr({
150 transform: d => `translate(${d.x}, ${d.y})`
151 });
152
153 this.linkGroup.selectAll('line')
154 .attr({
155 x1: l => l.source.x || 0,
156 y1: l => l.source.y || 0,
157 x2: l => l.target.x || 0,
158 y2: l => l.target.y || 0,
159 });
160 };
Matteo Scandolo0e8a8422017-03-25 14:55:40 -0700161 const getLinkStrenght = (l: IXosServiceGraphLink) => {
Matteo Scandolo0e8a8422017-03-25 14:55:40 -0700162 return 1;
163 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800164 const svgDim = this.getSvgDimensions();
165 this.forceLayout = d3.layout.force()
Max Chu2bfddde2017-06-29 13:41:52 -0700166 .size([svgDim.width, svgDim.height])
Matteo Scandolo75171782017-03-08 14:17:01 -0800167 .linkDistance(config.force.linkDistance)
Matteo Scandolo0e8a8422017-03-25 14:55:40 -0700168 .linkStrength(l => getLinkStrenght(l))
Matteo Scandolo75171782017-03-08 14:17:01 -0800169 .charge(config.force.charge)
170 .gravity(config.force.gravity)
171 .on('tick', tick);
172 }
173
174 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
175 this.forceLayout
176 .nodes(data.nodes)
177 .links(data.links)
178 .start();
179 }
180
Matteo Scandolo75171782017-03-08 14:17:01 -0800181 private renderServiceNodes(nodes: any) {
182
183 const self = this;
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700184
Matteo Scandolo75171782017-03-08 14:17:01 -0800185 nodes.append('rect')
186 .attr({
187 rx: config.node.radius,
188 ry: config.node.radius
189 });
190
191 nodes.append('text')
192 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700193 'text-anchor': 'middle',
194 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo75171782017-03-08 14:17:01 -0800195 })
196 .text(n => n.label);
197 // .text(n => `${n.id} - ${n.label}`);
198
199 const existing = nodes.selectAll('rect');
200
201 // resize node > rect as contained text
202 existing.each(function() {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700203 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo75171782017-03-08 14:17:01 -0800204 const rect = d3.select(this);
205 rect.attr({
206 width: textBBox.width + config.node.padding,
207 height: textBBox.height + config.node.padding,
208 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700209 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo75171782017-03-08 14:17:01 -0800210 });
211 });
212 }
213
214 private renderTenantNodes(nodes: any) {
215 nodes.append('rect')
216 .attr({
217 width: 40,
218 height: 40,
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700219 x: -20,
220 y: -20,
Matteo Scandolo75171782017-03-08 14:17:01 -0800221 transform: `rotate(45)`
222 });
223
224 nodes.append('text')
225 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700226 'text-anchor': 'middle',
227 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo75171782017-03-08 14:17:01 -0800228 })
229 .text(n => n.label);
230 }
231
232 private renderNetworkNodes(nodes: any) {
233 const self = this;
Simon Huntc8f23142017-03-14 14:11:13 -0700234
235 nodes.append('use')
236 .attr({
237 class: 'symbol-bg',
238 'xlink:href': '#cloud_bg'
239 });
240
241 nodes.append('use')
242 .attr({
243 class: 'symbol',
244 'xlink:href': '#cloud'
245 });
Matteo Scandolo75171782017-03-08 14:17:01 -0800246
247 nodes.append('text')
248 .attr({
Simon Huntc8f23142017-03-14 14:11:13 -0700249 'text-anchor': 'middle',
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700250 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo75171782017-03-08 14:17:01 -0800251 })
252 .text(n => n.label);
253
Simon Huntc8f23142017-03-14 14:11:13 -0700254 const existing = nodes.selectAll('use');
Matteo Scandolo75171782017-03-08 14:17:01 -0800255
256 // resize node > rect as contained text
257 existing.each(function() {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700258 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Simon Huntc8f23142017-03-14 14:11:13 -0700259 const useElem = d3.select(this);
260 const w = textBBox.width + config.node.padding * 2;
261 const h = w;
262 const xoff = -(w / 2);
263 const yoff = -(h / 2);
264
265 useElem.attr({
266 width: w,
267 height: h,
268 transform: 'translate(' + xoff + ',' + yoff + ')'
Matteo Scandolo75171782017-03-08 14:17:01 -0800269 });
270 });
271 }
272
273 private renderSubscriberNodes(nodes: any) {
274 const self = this;
275 nodes.append('rect');
276
277 nodes.append('text')
278 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700279 'text-anchor': 'middle',
280 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo75171782017-03-08 14:17:01 -0800281 })
282 .text(n => n.label);
283
284 const existing = nodes.selectAll('rect');
285
286 // resize node > rect as contained text
287 existing.each(function() {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700288 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo75171782017-03-08 14:17:01 -0800289 const rect = d3.select(this);
290 rect.attr({
291 width: textBBox.width + config.node.padding,
292 height: textBBox.height + config.node.padding,
293 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700294 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo75171782017-03-08 14:17:01 -0800295 });
296 });
297 }
298
299 private renderNodes(nodes: IXosServiceGraphNode[]) {
300 const node = this.nodeGroup
301 .selectAll('g.node')
302 .data(nodes, n => n.id);
303
Matteo Scandolo520a8a12017-03-10 17:31:37 -0800304 let mouseEventsTimer, selectedModel;
Matteo Scandolo75171782017-03-08 14:17:01 -0800305 const svgDim = this.getSvgDimensions();
306 const hStep = svgDim.width / (nodes.length - 1);
Max Chu2bfddde2017-06-29 13:41:52 -0700307 const vStep = svgDim.height / (nodes.length - 1);
Matteo Scandolo75171782017-03-08 14:17:01 -0800308 const entering = node.enter()
309 .append('g')
310 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700311 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700312 class: n => `node ${n.type} ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Matteo Scandolo75171782017-03-08 14:17:01 -0800313 transform: (n, i) => `translate(${hStep * i}, ${vStep * i})`
314 })
315 .call(this.forceLayout.drag)
316 .on('mousedown', () => {
Matteo Scandolo520a8a12017-03-10 17:31:37 -0800317 mouseEventsTimer = new Date().getTime();
Matteo Scandolo75171782017-03-08 14:17:01 -0800318 d3.event.stopPropagation();
319 })
Matteo Scandolo520a8a12017-03-10 17:31:37 -0800320 .on('mouseup', (n) => {
321 mouseEventsTimer = new Date().getTime() - mouseEventsTimer;
322 n.fixed = true;
323 })
324 .on('click', (n: IXosServiceGraphNode) => {
325 if (mouseEventsTimer > 100) {
326 // it is a drag
327 return;
328 }
329 if (selectedModel === n.id) {
330 // this model is already selected, so close the panel
331 this.XosSidePanel.removeInjectedComponents();
332 selectedModel = null;
333 return;
334 }
335 selectedModel = n.id;
336 const modelName = n.model['class_names'].split(',')[0];
337 const formConfig = this.XosModelDiscoverer.get(modelName).formCfg;
338 const model = angular.copy(n.model);
339 delete model.d3Id;
340 this.XosSidePanel.injectComponent('xosForm', {config: formConfig, ngModel: model});
Matteo Scandolo75171782017-03-08 14:17:01 -0800341 });
342
343 this.renderServiceNodes(entering.filter('.service'));
Matteo Scandolo72181592017-07-25 14:49:40 -0700344 this.renderTenantNodes(entering.filter('.serviceinstance'));
Matteo Scandolo75171782017-03-08 14:17:01 -0800345 this.renderNetworkNodes(entering.filter('.network'));
346 this.renderSubscriberNodes(entering.filter('.subscriber'));
Matteo Scandolo72181592017-07-25 14:49:40 -0700347 // this.renderSubscriberNodes(entering.filter('.tenantroot'));
Matteo Scandolo75171782017-03-08 14:17:01 -0800348 }
349
350 private renderLinks(links: IXosServiceGraphLink[]) {
351 const link = this.linkGroup
352 .selectAll('line')
353 .data(links, l => l.id);
354
355 const entering = link.enter();
356
357 entering.append('line')
358 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700359 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700360 class: n => `link ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Matteo Scandolo75171782017-03-08 14:17:01 -0800361 'marker-start': 'url(#arrow)'
362 });
363 }
364}
365
366export const XosFineGrainedTenancyGraph: angular.IComponentOptions = {
367 template: require('./fine-grained.component.html'),
368 controllerAs: 'vm',
369 controller: XosFineGrainedTenancyGraphCtrl,
370};