blob: 501c543e4ad86b7a0e72d56da8770151fd77777e [file] [log] [blame]
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -08001import './coarse.component.scss';
2import * as d3 from 'd3';
3import * as $ from 'jquery';
Matteo Scandolobafd8d62017-03-29 23:23:00 -07004import * as _ from 'lodash';
Matteo Scandolo72181592017-07-25 14:49:40 -07005import {IXosServiceGraphStore} from '../../services/service-graph.store';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -08006import {IXosServiceGraph, IXosServiceGraphNode, IXosServiceGraphLink} from '../../interfaces';
7import {XosServiceGraphConfig as config} from '../../graph.config';
Matteo Scandoloa160eef2017-03-06 17:21:26 -08008import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
9import {Subscription} from 'rxjs';
Matteo Scandolo7629cc42017-03-13 14:12:15 -070010import {IXosGraphHelpers} from '../../services/d3-helpers/graph.helpers';
Matteo Scandolobafd8d62017-03-29 23:23:00 -070011import {IXosServiceGraphReducer, IXosServiceGraphExtender} from '../../services/graph.extender';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080012
Matteo Scandolo968e7f22017-03-03 11:49:18 -080013class XosCoarseTenancyGraphCtrl {
14
Matteo Scandoloa160eef2017-03-06 17:21:26 -080015 static $inject = [
16 '$log',
17 'XosServiceGraphStore',
Matteo Scandolo7629cc42017-03-13 14:12:15 -070018 'XosDebouncer',
Matteo Scandolobafd8d62017-03-29 23:23:00 -070019 'XosGraphHelpers',
20 'XosServiceGraphExtender'
Matteo Scandoloa160eef2017-03-06 17:21:26 -080021 ];
Matteo Scandolo968e7f22017-03-03 11:49:18 -080022
23 public graph: IXosServiceGraph;
24
Matteo Scandoloa160eef2017-03-06 17:21:26 -080025 private CoarseGraphSubscription: Subscription;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080026 private svg;
27 private forceLayout;
28 private linkGroup;
29 private nodeGroup;
Matteo Scandolo6a7435f2017-03-24 18:07:17 -070030 private textSize = 20;
31 private textOffset = this.textSize / 4;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080032
Matteo Scandolo75171782017-03-08 14:17:01 -080033 // debounced functions
Matteo Scandoloa160eef2017-03-06 17:21:26 -080034 private renderGraph;
35
Matteo Scandolo968e7f22017-03-03 11:49:18 -080036 constructor (
37 private $log: ng.ILogService,
Matteo Scandoloa160eef2017-03-06 17:21:26 -080038 private XosServiceGraphStore: IXosServiceGraphStore,
Matteo Scandolo7629cc42017-03-13 14:12:15 -070039 private XosDebouncer: IXosDebouncer,
Matteo Scandolobafd8d62017-03-29 23:23:00 -070040 private XosGraphHelpers: IXosGraphHelpers,
41 private XosServiceGraphExtender: IXosServiceGraphExtender
Matteo Scandolo968e7f22017-03-03 11:49:18 -080042 ) {
43
Matteo Scandoloa160eef2017-03-06 17:21:26 -080044 }
45
46 $onInit() {
47 this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 500, this);
48
49 this.CoarseGraphSubscription = this.XosServiceGraphStore.getCoarse()
50 .subscribe(
Matteo Scandolobafd8d62017-03-29 23:23:00 -070051 (graph: IXosServiceGraph) => {
52 this.$log.debug(`[XosCoarseTenancyGraph] Coarse Event and render`, graph);
Matteo Scandolo75171782017-03-08 14:17:01 -080053
Matteo Scandoloa160eef2017-03-06 17:21:26 -080054 // id there are no data, do nothing
Matteo Scandolobafd8d62017-03-29 23:23:00 -070055 if (graph.nodes.length === 0) {
Matteo Scandoloa160eef2017-03-06 17:21:26 -080056 return;
57 }
Matteo Scandolobafd8d62017-03-29 23:23:00 -070058 this.graph = graph;
59
60 _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
61 graph = r.reducer(graph);
62 });
Matteo Scandoloa160eef2017-03-06 17:21:26 -080063 this.renderGraph();
64 },
65 err => {
66 this.$log.error(`[XosCoarseTenancyGraph] Coarse Event error`, err);
67 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080068
69 this.handleSvg();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080070 this.setupForceLayout();
71
72 $(window).on('resize', () => {
73 this.setupForceLayout();
74 this.renderGraph();
75 });
76 }
77
78 $onDestroy() {
79 this.CoarseGraphSubscription.unsubscribe();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080080 }
81
82 private _renderGraph() {
Matteo Scandolo9b460042017-04-14 16:24:45 -070083 if (!angular.isDefined(this.graph) || !angular.isDefined(this.graph.nodes) || !angular.isDefined(this.graph.links)) {
84 return;
85 }
Matteo Scandoloa160eef2017-03-06 17:21:26 -080086 this.addNodeLinksToForceLayout(this.graph);
87 this.renderNodes(this.graph.nodes);
88 this.renderLinks(this.graph.links);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080089 }
90
Max Chu2bfddde2017-06-29 13:41:52 -070091 private getSvgDimensions(): {width: number, height: number} {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080092 return {
Matteo Scandolo9b460042017-04-14 16:24:45 -070093 width: $('xos-coarse-tenancy-graph svg').width() || 0,
Max Chu2bfddde2017-06-29 13:41:52 -070094 height: $('xos-coarse-tenancy-graph svg').height() || 0
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080095 };
96 }
97
98 private handleSvg() {
99 this.svg = d3.select('svg');
100
101 this.svg.append('svg:defs')
102 .selectAll('marker')
103 .data(config.markers)
104 .enter()
105 .append('svg:marker')
106 .attr('id', d => d.id)
107 .attr('viewBox', d => d.viewBox)
108 .attr('refX', d => d.refX)
109 .attr('refY', d => d.refY)
110 .attr('markerWidth', d => d.width)
111 .attr('markerHeight', d => d.height)
112 .attr('orient', 'auto')
113 .attr('class', d => `${d.id}-marker`)
114 .append('svg:path')
115 .attr('d', d => d.path);
116
117 this.linkGroup = this.svg.append('g')
118 .attr({
119 class: 'link-group'
120 });
121
122 this.nodeGroup = this.svg.append('g')
123 .attr({
124 class: 'node-group'
125 });
126 }
127
Max Chu2bfddde2017-06-29 13:41:52 -0700128 private collide(n: any) {
129 const svgDim = this.getSvgDimensions();
130 const x = Math.max(n.width / 2, Math.min(n.x, svgDim.width - (n.width / 2)));
131 const y = Math.max(n.height / 2, Math.min(n.y, svgDim.height - (n.height / 2)));
132 return `${x}, ${y}`;
133 }
134
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800135 private setupForceLayout() {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800136
Max Chu2bfddde2017-06-29 13:41:52 -0700137 let svgDim = this.getSvgDimensions();
138
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800139 const tick = () => {
Max Chu2bfddde2017-06-29 13:41:52 -0700140
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800141 this.nodeGroup.selectAll('g.node')
142 .attr({
Max Chu2bfddde2017-06-29 13:41:52 -0700143 transform: d => `translate(${this.collide(d)})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800144 });
145
146 this.linkGroup.selectAll('line')
147 .attr({
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800148 x1: l => l.source.x || 0,
149 y1: l => l.source.y || 0,
150 x2: l => l.target.x || 0,
151 y2: l => l.target.y || 0,
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800152 });
153 };
Max Chu2bfddde2017-06-29 13:41:52 -0700154
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800155 this.forceLayout = d3.layout.force()
Max Chu2bfddde2017-06-29 13:41:52 -0700156 .size([svgDim.width, svgDim.height])
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800157 .linkDistance(config.force.linkDistance)
158 .charge(config.force.charge)
159 .gravity(config.force.gravity)
160 .on('tick', tick);
161 }
162
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800163 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
164 this.forceLayout
165 .nodes(data.nodes)
166 .links(data.links)
167 .start();
168 }
169
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800170 private renderNodes(nodes: IXosServiceGraphNode[]) {
171 const self = this;
172 const node = this.nodeGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800173 .selectAll('g.node')
174 .data(nodes, n => n.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800175
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800176 const svgDim = this.getSvgDimensions();
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800177 const entering = node.enter()
178 .append('g')
179 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700180 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700181 class: n => `node ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Max Chu2bfddde2017-06-29 13:41:52 -0700182 transform: `translate(${svgDim.width / 2}, ${svgDim.height / 2})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800183 })
184 .call(this.forceLayout.drag)
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800185 .on('mousedown', () => {
186 d3.event.stopPropagation();
187 })
188 .on('mouseup', (d) => {
189 d.fixed = true;
190 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800191
192 entering.append('rect')
193 .attr({
194 rx: config.node.radius,
195 ry: config.node.radius
196 });
197
198 entering.append('text')
199 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700200 'text-anchor': 'middle',
201 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800202 })
203 .text(n => n.label);
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800204 // .text(n => `${n.id} - ${n.label}`);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800205
206 const existing = node.selectAll('rect');
207
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800208 // resize node > rect as contained text
Max Chu2bfddde2017-06-29 13:41:52 -0700209
210 existing.each(function(d: any) {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700211 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800212 const rect = d3.select(this);
213 rect.attr({
214 width: textBBox.width + config.node.padding,
215 height: textBBox.height + config.node.padding,
216 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700217 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800218 });
Max Chu2bfddde2017-06-29 13:41:52 -0700219 d.width = textBBox.width + config.node.padding;
220 d.height = textBBox.height + config.node.padding;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800221 });
Max Chu2bfddde2017-06-29 13:41:52 -0700222
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800223 }
224
225 private renderLinks(links: IXosServiceGraphLink[]) {
226 const link = this.linkGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800227 .selectAll('line')
228 .data(links, l => l.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800229
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800230 const entering = link.enter();
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700231
232 // TODO read classes from graph links
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800233
234 entering.append('line')
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800235 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700236 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700237 class: l => `link ${this.XosGraphHelpers.parseElemClasses(l.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800238 'marker-start': 'url(#arrow)'
239 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800240 }
241}
242
243export const XosCoarseTenancyGraph: angular.IComponentOptions = {
244 template: require('./coarse.component.html'),
245 controllerAs: 'vm',
246 controller: XosCoarseTenancyGraphCtrl,
247};