blob: c7a7ac5f3f1453fbe5846bd35e9c9a0d4f2ed166 [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 Scandolo968e7f22017-03-03 11:49:18 -08005import {IXosServiceGraphStore} from '../../services/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
91 private getSvgDimensions(): {width: number, heigth: number} {
92 return {
Matteo Scandolo9b460042017-04-14 16:24:45 -070093 width: $('xos-coarse-tenancy-graph svg').width() || 0,
94 heigth: $('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
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800128 private setupForceLayout() {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800129
130 const tick = () => {
131 this.nodeGroup.selectAll('g.node')
132 .attr({
133 transform: d => `translate(${d.x}, ${d.y})`
134 });
135
136 this.linkGroup.selectAll('line')
137 .attr({
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800138 x1: l => l.source.x || 0,
139 y1: l => l.source.y || 0,
140 x2: l => l.target.x || 0,
141 y2: l => l.target.y || 0,
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800142 });
143 };
144 const svgDim = this.getSvgDimensions();
145 this.forceLayout = d3.layout.force()
146 .size([svgDim.width, svgDim.heigth])
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800147 .linkDistance(config.force.linkDistance)
148 .charge(config.force.charge)
149 .gravity(config.force.gravity)
150 .on('tick', tick);
151 }
152
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800153 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
154 this.forceLayout
155 .nodes(data.nodes)
156 .links(data.links)
157 .start();
158 }
159
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800160 private renderNodes(nodes: IXosServiceGraphNode[]) {
161 const self = this;
162 const node = this.nodeGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800163 .selectAll('g.node')
164 .data(nodes, n => n.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800165
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800166 const svgDim = this.getSvgDimensions();
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800167 const entering = node.enter()
168 .append('g')
169 .attr({
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700170 class: n => `node ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800171 transform: `translate(${svgDim.width / 2}, ${svgDim.heigth / 2})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800172 })
173 .call(this.forceLayout.drag)
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800174 .on('mousedown', () => {
175 d3.event.stopPropagation();
176 })
177 .on('mouseup', (d) => {
178 d.fixed = true;
179 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800180
181 entering.append('rect')
182 .attr({
183 rx: config.node.radius,
184 ry: config.node.radius
185 });
186
187 entering.append('text')
188 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700189 'text-anchor': 'middle',
190 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800191 })
192 .text(n => n.label);
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800193 // .text(n => `${n.id} - ${n.label}`);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800194
195 const existing = node.selectAll('rect');
196
197
198 // resize node > rect as contained text
199 existing.each(function() {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700200 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800201 const rect = d3.select(this);
202 rect.attr({
203 width: textBBox.width + config.node.padding,
204 height: textBBox.height + config.node.padding,
205 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700206 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800207 });
208 });
209 }
210
211 private renderLinks(links: IXosServiceGraphLink[]) {
212 const link = this.linkGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800213 .selectAll('line')
214 .data(links, l => l.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800215
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800216 const entering = link.enter();
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700217
218 // TODO read classes from graph links
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800219
220 entering.append('line')
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800221 .attr({
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700222 class: l => `link ${this.XosGraphHelpers.parseElemClasses(l.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800223 'marker-start': 'url(#arrow)'
224 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800225 }
226}
227
228export const XosCoarseTenancyGraph: angular.IComponentOptions = {
229 template: require('./coarse.component.html'),
230 controllerAs: 'vm',
231 controller: XosCoarseTenancyGraphCtrl,
232};