blob: ed3665448863d3e57271305c26bc75e3a4c660b3 [file] [log] [blame]
Matteo Scandolo8cf33a32017-11-14 15:52:29 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import './graph.component.scss';
18
19import * as d3 from 'd3';
20import * as $ from 'jquery';
21
22import {IXosGraphStore} from '../../services/graph.store';
23import {Subscription} from 'rxjs/Subscription';
24import {XosServiceGraphConfig as config} from '../../graph.config';
25import {IXosGraphHelpers} from '../../services/d3-helpers/graph-elements.helpers';
26import {IXosServiceGraphIcons} from '../../services/d3-helpers/graph-icons.service';
27import {IXosNodePositioner} from '../../services/node-positioner.service';
28import {IXosNodeRenderer} from '../../services/renderer/node.renderer';
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080029import {IXosSgLink, IXosSgNode} from '../../interfaces';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080030import {IXosGraphConfig} from '../../services/graph.config';
31
32class XosServiceGraphCtrl {
33 static $inject = [
34 '$log',
35 '$scope',
36 'XosGraphStore',
37 'XosGraphHelpers',
38 'XosServiceGraphIcons',
39 'XosNodePositioner',
40 'XosNodeRenderer',
41 'XosGraphConfig'
42 ];
43
44 public loader: boolean = true;
45
46 private GraphSubscription: Subscription;
47 private graph: any; // this is the Graph instance
48
49 // graph element
50 private svg;
51 private linkGroup;
52 private nodeGroup;
53 private forceLayout;
54
55 constructor (
56 private $log: ng.ILogService,
57 private $scope: ng.IScope,
58 private XosGraphStore: IXosGraphStore,
59 private XosGraphHelpers: IXosGraphHelpers,
60 private XosServiceGraphIcons: IXosServiceGraphIcons,
61 private XosNodePositioner: IXosNodePositioner,
62 private XosNodeRenderer: IXosNodeRenderer,
63 private XosGraphConfig: IXosGraphConfig
64 ) {
65 this.$log.info('[XosServiceGraph] Component setup');
66
67 this.XosGraphConfig.setupKeyboardShortcuts();
68
69 this.setupSvg();
70 this.setupForceLayout();
71
72 this.GraphSubscription = this.XosGraphStore.get()
73 .subscribe(
74 graph => {
75 this.graph = graph;
76 if (this.graph.nodes().length > 0) {
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080077 this.renderGraph(this.graph);
78 }
79 },
80 error => {
81 this.$log.error('[XosServiceGraph] XosGraphStore observable error: ', error);
82 }
83 );
84
85 this.$scope.$on('xos.sg.update', () => {
86 this.$log.info(`[XosServiceGraph] Received event: xos.sg.update`);
87 this.renderGraph(this.graph);
88 });
Matteo Scandolo35fdf242017-11-30 12:29:45 -080089
90 $(window).resize(() => {
91 this.renderGraph(this.graph);
92 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080093 }
94
95 $onDestroy() {
96 this.GraphSubscription.unsubscribe();
97 }
98
99 public closeFullscreen() {
100 this.XosGraphConfig.toggleFullscreen();
101 }
102
103 private setupSvg() {
104 this.svg = d3.select('xos-service-graph svg');
105
106 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
117 private setupForceLayout() {
118 this.$log.debug(`[XosServiceGraph] Setup Force Layout`);
119 const tick = () => {
120 this.nodeGroup.selectAll('g.node')
121 .attr({
122 transform: d => `translate(${d.x}, ${d.y})`
123 });
124
125 this.linkGroup.selectAll('line')
126 .attr({
127 x1: l => l.source.x || 0,
128 y1: l => l.source.y || 0,
129 x2: l => l.target.x || 0,
130 y2: l => l.target.y || 0,
131 });
132 };
133
134 const svgDim = this.getSvgDimensions();
135
136 this.forceLayout =
137 d3.layout.force()
138 .size([svgDim.width, svgDim.height])
139 .on('tick', tick);
140 }
141
142 private getSvgDimensions(): {width: number, height: number} {
143 return {
144 width: $('xos-service-graph svg').width(),
145 height: $('xos-service-graph svg').height()
146 };
147 }
148
149 private renderGraph(graph: any) {
150 let nodes: IXosSgNode[] = this.XosGraphStore.nodesFromGraph(graph);
151 let links = this.XosGraphStore.linksFromGraph(graph);
152 const svgDim = this.getSvgDimensions();
153
154 this.XosNodePositioner.positionNodes(svgDim, nodes)
155 .then((nodes: IXosSgNode[]) => {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800156 this.loader = false;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800157
158 this.forceLayout
159 .nodes(nodes)
160 .links(links)
161 .size([svgDim.width, svgDim.height])
162 .linkDistance(config.force.linkDistance)
163 .charge(config.force.charge)
164 .gravity(config.force.gravity)
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800165 .linkStrength((link: IXosSgLink) => {
166 switch (link.type) {
167 case 'ownership':
168 case 'instance_ownership':
169 // NOTE make "ownsership" links stronger than other for positioning
170 return 1;
171 }
172 return 0.1;
173 })
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800174 .start();
175
176 // render nodes
177 this.XosNodeRenderer.renderNodes(this.forceLayout, this.nodeGroup, nodes);
178 this.renderLinks(links);
179 });
180 }
181
182 private renderLinks(links: any[]) {
183
184 const link = this.linkGroup
185 .selectAll('line')
186 .data(links, l => l.id);
187
188 const entering = link.enter();
189
190 entering.append('line')
191 .attr({
192 id: n => n.id,
193 class: n => n.type
194 });
195
196 link.exit().remove();
197 }
198
199}
200
201export const XosServiceGraph: angular.IComponentOptions = {
202 template: require('./graph.component.html'),
203 controllerAs: 'vm',
204 controller: XosServiceGraphCtrl,
205};