blob: d02bb23c436850a931823fd54512055e96fd7de8 [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() {
83 this.addNodeLinksToForceLayout(this.graph);
84 this.renderNodes(this.graph.nodes);
85 this.renderLinks(this.graph.links);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080086 }
87
88 private getSvgDimensions(): {width: number, heigth: number} {
89 return {
90 width: $('xos-coarse-tenancy-graph svg').width(),
91 heigth: $('xos-coarse-tenancy-graph svg').height()
92 };
93 }
94
95 private handleSvg() {
96 this.svg = d3.select('svg');
97
98 this.svg.append('svg:defs')
99 .selectAll('marker')
100 .data(config.markers)
101 .enter()
102 .append('svg:marker')
103 .attr('id', d => d.id)
104 .attr('viewBox', d => d.viewBox)
105 .attr('refX', d => d.refX)
106 .attr('refY', d => d.refY)
107 .attr('markerWidth', d => d.width)
108 .attr('markerHeight', d => d.height)
109 .attr('orient', 'auto')
110 .attr('class', d => `${d.id}-marker`)
111 .append('svg:path')
112 .attr('d', d => d.path);
113
114 this.linkGroup = this.svg.append('g')
115 .attr({
116 class: 'link-group'
117 });
118
119 this.nodeGroup = this.svg.append('g')
120 .attr({
121 class: 'node-group'
122 });
123 }
124
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800125 private setupForceLayout() {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800126
127 const tick = () => {
128 this.nodeGroup.selectAll('g.node')
129 .attr({
130 transform: d => `translate(${d.x}, ${d.y})`
131 });
132
133 this.linkGroup.selectAll('line')
134 .attr({
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800135 x1: l => l.source.x || 0,
136 y1: l => l.source.y || 0,
137 x2: l => l.target.x || 0,
138 y2: l => l.target.y || 0,
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800139 });
140 };
141 const svgDim = this.getSvgDimensions();
142 this.forceLayout = d3.layout.force()
143 .size([svgDim.width, svgDim.heigth])
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800144 .linkDistance(config.force.linkDistance)
145 .charge(config.force.charge)
146 .gravity(config.force.gravity)
147 .on('tick', tick);
148 }
149
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800150 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
151 this.forceLayout
152 .nodes(data.nodes)
153 .links(data.links)
154 .start();
155 }
156
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800157 private renderNodes(nodes: IXosServiceGraphNode[]) {
158 const self = this;
159 const node = this.nodeGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800160 .selectAll('g.node')
161 .data(nodes, n => n.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800162
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800163 const svgDim = this.getSvgDimensions();
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800164 const entering = node.enter()
165 .append('g')
166 .attr({
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700167 class: n => `node ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800168 transform: `translate(${svgDim.width / 2}, ${svgDim.heigth / 2})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800169 })
170 .call(this.forceLayout.drag)
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800171 .on('mousedown', () => {
172 d3.event.stopPropagation();
173 })
174 .on('mouseup', (d) => {
175 d.fixed = true;
176 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800177
178 entering.append('rect')
179 .attr({
180 rx: config.node.radius,
181 ry: config.node.radius
182 });
183
184 entering.append('text')
185 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700186 'text-anchor': 'middle',
187 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800188 })
189 .text(n => n.label);
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800190 // .text(n => `${n.id} - ${n.label}`);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800191
192 const existing = node.selectAll('rect');
193
194
195 // resize node > rect as contained text
196 existing.each(function() {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700197 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800198 const rect = d3.select(this);
199 rect.attr({
200 width: textBBox.width + config.node.padding,
201 height: textBBox.height + config.node.padding,
202 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700203 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800204 });
205 });
206 }
207
208 private renderLinks(links: IXosServiceGraphLink[]) {
209 const link = this.linkGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800210 .selectAll('line')
211 .data(links, l => l.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800212
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800213 const entering = link.enter();
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700214
215 // TODO read classes from graph links
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800216
217 entering.append('line')
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800218 .attr({
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700219 class: l => `link ${this.XosGraphHelpers.parseElemClasses(l.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800220 'marker-start': 'url(#arrow)'
221 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800222 }
223}
224
225export const XosCoarseTenancyGraph: angular.IComponentOptions = {
226 template: require('./coarse.component.html'),
227 controllerAs: 'vm',
228 controller: XosCoarseTenancyGraphCtrl,
229};