blob: c8e23613feaa063103f401693bd97bc4444f8ddb [file] [log] [blame]
Matteo Scandolofb46ae62017-08-08 09:10:50 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080019import './coarse.component.scss';
20import * as d3 from 'd3';
21import * as $ from 'jquery';
Matteo Scandolobafd8d62017-03-29 23:23:00 -070022import * as _ from 'lodash';
Matteo Scandolo72181592017-07-25 14:49:40 -070023import {IXosServiceGraphStore} from '../../services/service-graph.store';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080024import {IXosServiceGraph, IXosServiceGraphNode, IXosServiceGraphLink} from '../../interfaces';
25import {XosServiceGraphConfig as config} from '../../graph.config';
Matteo Scandoloa160eef2017-03-06 17:21:26 -080026import {IXosDebouncer} from '../../../core/services/helpers/debounce.helper';
27import {Subscription} from 'rxjs';
Matteo Scandolo7629cc42017-03-13 14:12:15 -070028import {IXosGraphHelpers} from '../../services/d3-helpers/graph.helpers';
Matteo Scandolobafd8d62017-03-29 23:23:00 -070029import {IXosServiceGraphReducer, IXosServiceGraphExtender} from '../../services/graph.extender';
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080030
Matteo Scandolo968e7f22017-03-03 11:49:18 -080031class XosCoarseTenancyGraphCtrl {
32
Matteo Scandoloa160eef2017-03-06 17:21:26 -080033 static $inject = [
34 '$log',
35 'XosServiceGraphStore',
Matteo Scandolo7629cc42017-03-13 14:12:15 -070036 'XosDebouncer',
Matteo Scandolobafd8d62017-03-29 23:23:00 -070037 'XosGraphHelpers',
38 'XosServiceGraphExtender'
Matteo Scandoloa160eef2017-03-06 17:21:26 -080039 ];
Matteo Scandolo968e7f22017-03-03 11:49:18 -080040
41 public graph: IXosServiceGraph;
42
Matteo Scandoloa160eef2017-03-06 17:21:26 -080043 private CoarseGraphSubscription: Subscription;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080044 private svg;
45 private forceLayout;
46 private linkGroup;
47 private nodeGroup;
Matteo Scandolo6a7435f2017-03-24 18:07:17 -070048 private textSize = 20;
49 private textOffset = this.textSize / 4;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080050
Matteo Scandolo75171782017-03-08 14:17:01 -080051 // debounced functions
Matteo Scandoloa160eef2017-03-06 17:21:26 -080052 private renderGraph;
53
Matteo Scandolo968e7f22017-03-03 11:49:18 -080054 constructor (
55 private $log: ng.ILogService,
Matteo Scandoloa160eef2017-03-06 17:21:26 -080056 private XosServiceGraphStore: IXosServiceGraphStore,
Matteo Scandolo7629cc42017-03-13 14:12:15 -070057 private XosDebouncer: IXosDebouncer,
Matteo Scandolobafd8d62017-03-29 23:23:00 -070058 private XosGraphHelpers: IXosGraphHelpers,
59 private XosServiceGraphExtender: IXosServiceGraphExtender
Matteo Scandolo968e7f22017-03-03 11:49:18 -080060 ) {
61
Matteo Scandoloa160eef2017-03-06 17:21:26 -080062 }
63
64 $onInit() {
65 this.renderGraph = this.XosDebouncer.debounce(this._renderGraph, 500, this);
66
67 this.CoarseGraphSubscription = this.XosServiceGraphStore.getCoarse()
68 .subscribe(
Matteo Scandolobafd8d62017-03-29 23:23:00 -070069 (graph: IXosServiceGraph) => {
70 this.$log.debug(`[XosCoarseTenancyGraph] Coarse Event and render`, graph);
Matteo Scandolo75171782017-03-08 14:17:01 -080071
Matteo Scandoloa160eef2017-03-06 17:21:26 -080072 // id there are no data, do nothing
Matteo Scandolobafd8d62017-03-29 23:23:00 -070073 if (graph.nodes.length === 0) {
Matteo Scandoloa160eef2017-03-06 17:21:26 -080074 return;
75 }
Matteo Scandolobafd8d62017-03-29 23:23:00 -070076 this.graph = graph;
77
78 _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
79 graph = r.reducer(graph);
80 });
Matteo Scandoloa160eef2017-03-06 17:21:26 -080081 this.renderGraph();
82 },
83 err => {
84 this.$log.error(`[XosCoarseTenancyGraph] Coarse Event error`, err);
85 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -080086
87 this.handleSvg();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080088 this.setupForceLayout();
89
90 $(window).on('resize', () => {
91 this.setupForceLayout();
92 this.renderGraph();
93 });
94 }
95
96 $onDestroy() {
97 this.CoarseGraphSubscription.unsubscribe();
Matteo Scandoloa160eef2017-03-06 17:21:26 -080098 }
99
100 private _renderGraph() {
Matteo Scandolo9b460042017-04-14 16:24:45 -0700101 if (!angular.isDefined(this.graph) || !angular.isDefined(this.graph.nodes) || !angular.isDefined(this.graph.links)) {
102 return;
103 }
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800104 this.addNodeLinksToForceLayout(this.graph);
105 this.renderNodes(this.graph.nodes);
106 this.renderLinks(this.graph.links);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800107 }
108
Max Chu2bfddde2017-06-29 13:41:52 -0700109 private getSvgDimensions(): {width: number, height: number} {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800110 return {
Matteo Scandolo9b460042017-04-14 16:24:45 -0700111 width: $('xos-coarse-tenancy-graph svg').width() || 0,
Max Chu2bfddde2017-06-29 13:41:52 -0700112 height: $('xos-coarse-tenancy-graph svg').height() || 0
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800113 };
114 }
115
116 private handleSvg() {
117 this.svg = d3.select('svg');
118
119 this.svg.append('svg:defs')
120 .selectAll('marker')
121 .data(config.markers)
122 .enter()
123 .append('svg:marker')
124 .attr('id', d => d.id)
125 .attr('viewBox', d => d.viewBox)
126 .attr('refX', d => d.refX)
127 .attr('refY', d => d.refY)
128 .attr('markerWidth', d => d.width)
129 .attr('markerHeight', d => d.height)
130 .attr('orient', 'auto')
131 .attr('class', d => `${d.id}-marker`)
132 .append('svg:path')
133 .attr('d', d => d.path);
134
135 this.linkGroup = this.svg.append('g')
136 .attr({
137 class: 'link-group'
138 });
139
140 this.nodeGroup = this.svg.append('g')
141 .attr({
142 class: 'node-group'
143 });
144 }
145
Max Chu2bfddde2017-06-29 13:41:52 -0700146 private collide(n: any) {
147 const svgDim = this.getSvgDimensions();
148 const x = Math.max(n.width / 2, Math.min(n.x, svgDim.width - (n.width / 2)));
149 const y = Math.max(n.height / 2, Math.min(n.y, svgDim.height - (n.height / 2)));
150 return `${x}, ${y}`;
151 }
152
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800153 private setupForceLayout() {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800154
Max Chu2bfddde2017-06-29 13:41:52 -0700155 let svgDim = this.getSvgDimensions();
156
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800157 const tick = () => {
Max Chu2bfddde2017-06-29 13:41:52 -0700158
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800159 this.nodeGroup.selectAll('g.node')
160 .attr({
Max Chu2bfddde2017-06-29 13:41:52 -0700161 transform: d => `translate(${this.collide(d)})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800162 });
163
164 this.linkGroup.selectAll('line')
165 .attr({
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800166 x1: l => l.source.x || 0,
167 y1: l => l.source.y || 0,
168 x2: l => l.target.x || 0,
169 y2: l => l.target.y || 0,
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800170 });
171 };
Max Chu2bfddde2017-06-29 13:41:52 -0700172
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800173 this.forceLayout = d3.layout.force()
Max Chu2bfddde2017-06-29 13:41:52 -0700174 .size([svgDim.width, svgDim.height])
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800175 .linkDistance(config.force.linkDistance)
176 .charge(config.force.charge)
177 .gravity(config.force.gravity)
178 .on('tick', tick);
179 }
180
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800181 private addNodeLinksToForceLayout(data: IXosServiceGraph) {
182 this.forceLayout
183 .nodes(data.nodes)
184 .links(data.links)
185 .start();
186 }
187
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800188 private renderNodes(nodes: IXosServiceGraphNode[]) {
189 const self = this;
190 const node = this.nodeGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800191 .selectAll('g.node')
192 .data(nodes, n => n.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800193
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800194 const svgDim = this.getSvgDimensions();
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800195 const entering = node.enter()
196 .append('g')
197 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700198 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700199 class: n => `node ${this.XosGraphHelpers.parseElemClasses(n.d3Class)}`,
Max Chu2bfddde2017-06-29 13:41:52 -0700200 transform: `translate(${svgDim.width / 2}, ${svgDim.height / 2})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800201 })
202 .call(this.forceLayout.drag)
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800203 .on('mousedown', () => {
204 d3.event.stopPropagation();
205 })
206 .on('mouseup', (d) => {
207 d.fixed = true;
208 });
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800209
210 entering.append('rect')
211 .attr({
212 rx: config.node.radius,
213 ry: config.node.radius
214 });
215
216 entering.append('text')
217 .attr({
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700218 'text-anchor': 'middle',
219 'transform': `translate(0,${this.textOffset})`
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800220 })
221 .text(n => n.label);
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800222 // .text(n => `${n.id} - ${n.label}`);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800223
224 const existing = node.selectAll('rect');
225
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800226 // resize node > rect as contained text
Max Chu2bfddde2017-06-29 13:41:52 -0700227
228 existing.each(function(d: any) {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700229 const textBBox = self.XosGraphHelpers.getSiblingTextBBox(this);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800230 const rect = d3.select(this);
231 rect.attr({
232 width: textBBox.width + config.node.padding,
233 height: textBBox.height + config.node.padding,
234 x: textBBox.x - (config.node.padding / 2),
Matteo Scandolo6a7435f2017-03-24 18:07:17 -0700235 y: (textBBox.y + self.textOffset) - (config.node.padding / 2)
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800236 });
Max Chu2bfddde2017-06-29 13:41:52 -0700237 d.width = textBBox.width + config.node.padding;
238 d.height = textBBox.height + config.node.padding;
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800239 });
Max Chu2bfddde2017-06-29 13:41:52 -0700240
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800241 }
242
243 private renderLinks(links: IXosServiceGraphLink[]) {
244 const link = this.linkGroup
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800245 .selectAll('line')
246 .data(links, l => l.id);
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800247
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800248 const entering = link.enter();
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700249
250 // TODO read classes from graph links
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800251
252 entering.append('line')
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800253 .attr({
Matteo Scandolo72181592017-07-25 14:49:40 -0700254 id: n => n.id,
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700255 class: l => `link ${this.XosGraphHelpers.parseElemClasses(l.d3Class)}`,
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800256 'marker-start': 'url(#arrow)'
257 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800258 }
259}
260
261export const XosCoarseTenancyGraph: angular.IComponentOptions = {
262 template: require('./coarse.component.html'),
263 controllerAs: 'vm',
264 controller: XosCoarseTenancyGraphCtrl,
265};