blob: ae5c2f1406cb35693d0dc921688478a176e6fe53 [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';
Matteo Scandolo865b11c2018-02-14 16:57:44 -080021import * as _ from 'lodash';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080022
23import {IXosGraphStore} from '../../services/graph.store';
24import {Subscription} from 'rxjs/Subscription';
25import {XosServiceGraphConfig as config} from '../../graph.config';
26import {IXosGraphHelpers} from '../../services/d3-helpers/graph-elements.helpers';
27import {IXosServiceGraphIcons} from '../../services/d3-helpers/graph-icons.service';
28import {IXosNodePositioner} from '../../services/node-positioner.service';
29import {IXosNodeRenderer} from '../../services/renderer/node.renderer';
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080030import {IXosSgLink, IXosSgNode} from '../../interfaces';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080031import {IXosGraphConfig} from '../../services/graph.config';
Matteo Scandolob8cdf552018-02-12 17:56:26 -080032import {GraphStates, IXosGraphStateMachine} from '../../services/graph-state-machine';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080033
34class XosServiceGraphCtrl {
35 static $inject = [
36 '$log',
37 '$scope',
38 'XosGraphStore',
39 'XosGraphHelpers',
40 'XosServiceGraphIcons',
41 'XosNodePositioner',
42 'XosNodeRenderer',
Matteo Scandolob8cdf552018-02-12 17:56:26 -080043 'XosGraphConfig',
44 'XosGraphStateMachine'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080045 ];
46
Matteo Scandolob8cdf552018-02-12 17:56:26 -080047 // graph status
48 public currentState: number;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080049 public loader: boolean = true;
50
51 private GraphSubscription: Subscription;
52 private graph: any; // this is the Graph instance
53
Matteo Scandolob8cdf552018-02-12 17:56:26 -080054
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080055 // graph element
56 private svg;
57 private linkGroup;
58 private nodeGroup;
59 private forceLayout;
60
61 constructor (
62 private $log: ng.ILogService,
63 private $scope: ng.IScope,
64 private XosGraphStore: IXosGraphStore,
65 private XosGraphHelpers: IXosGraphHelpers,
66 private XosServiceGraphIcons: IXosServiceGraphIcons,
67 private XosNodePositioner: IXosNodePositioner,
68 private XosNodeRenderer: IXosNodeRenderer,
Matteo Scandolob8cdf552018-02-12 17:56:26 -080069 private XosGraphConfig: IXosGraphConfig,
70 public XosGraphStateMachine: IXosGraphStateMachine
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080071 ) {
72 this.$log.info('[XosServiceGraph] Component setup');
73
Matteo Scandolob8cdf552018-02-12 17:56:26 -080074 this.currentState = this.XosGraphStateMachine.getCurrentState();
75
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080076 this.XosGraphConfig.setupKeyboardShortcuts();
77
78 this.setupSvg();
79 this.setupForceLayout();
80
81 this.GraphSubscription = this.XosGraphStore.get()
82 .subscribe(
83 graph => {
84 this.graph = graph;
85 if (this.graph.nodes().length > 0) {
Matteo Scandolob8cdf552018-02-12 17:56:26 -080086 this.$log.info('[XosServiceGraph] Rendering graph: ', this.graph.nodes(), this.graph.edges());
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080087 this.renderGraph(this.graph);
88 }
89 },
90 error => {
91 this.$log.error('[XosServiceGraph] XosGraphStore observable error: ', error);
92 }
93 );
94
95 this.$scope.$on('xos.sg.update', () => {
96 this.$log.info(`[XosServiceGraph] Received event: xos.sg.update`);
97 this.renderGraph(this.graph);
98 });
Matteo Scandolo35fdf242017-11-30 12:29:45 -080099
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800100 this.$scope.$on('xos.sg.stateChange', (event, state: number) => {
101 this.$log.info(`[XosServiceGraph] Received event: xos.sg.stateChange. New state is ${state}`);
102 this.$scope.$applyAsync(() => {
103 this.currentState = state;
104 });
105 });
106
Matteo Scandolo35fdf242017-11-30 12:29:45 -0800107 $(window).resize(() => {
108 this.renderGraph(this.graph);
109 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800110 }
111
112 $onDestroy() {
113 this.GraphSubscription.unsubscribe();
114 }
115
116 public closeFullscreen() {
117 this.XosGraphConfig.toggleFullscreen();
118 }
119
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800120 public toggleModel(modelName: string): void {
121 switch (modelName) {
122 case 'services':
123 this.XosGraphStateMachine.go(GraphStates.Services);
124 break;
125 case 'serviceinstances':
126 this.XosGraphStateMachine.go(GraphStates.ServiceInstances);
127 break;
128 case 'instances':
129 this.XosGraphStateMachine.go(GraphStates.Instances);
130 break;
131 case 'networks':
132 this.XosGraphStateMachine.go(GraphStates.Networks);
133 break;
134 }
135 }
136
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800137 private setupSvg() {
138 this.svg = d3.select('xos-service-graph svg');
139
140 this.linkGroup = this.svg.append('g')
141 .attr({
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800142 'class': 'link-group'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800143 });
144
145 this.nodeGroup = this.svg.append('g')
146 .attr({
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800147 'class': 'node-group'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800148 });
149 }
150
151 private setupForceLayout() {
152 this.$log.debug(`[XosServiceGraph] Setup Force Layout`);
153 const tick = () => {
154 this.nodeGroup.selectAll('g.node')
155 .attr({
156 transform: d => `translate(${d.x}, ${d.y})`
157 });
158
159 this.linkGroup.selectAll('line')
160 .attr({
161 x1: l => l.source.x || 0,
162 y1: l => l.source.y || 0,
163 x2: l => l.target.x || 0,
164 y2: l => l.target.y || 0,
165 });
166 };
167
168 const svgDim = this.getSvgDimensions();
169
170 this.forceLayout =
171 d3.layout.force()
172 .size([svgDim.width, svgDim.height])
173 .on('tick', tick);
174 }
175
176 private getSvgDimensions(): {width: number, height: number} {
177 return {
178 width: $('xos-service-graph svg').width(),
179 height: $('xos-service-graph svg').height()
180 };
181 }
182
183 private renderGraph(graph: any) {
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800184
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800185 let nodes: IXosSgNode[] = this.XosGraphStore.nodesFromGraph(graph);
186 let links = this.XosGraphStore.linksFromGraph(graph);
187 const svgDim = this.getSvgDimensions();
188
189 this.XosNodePositioner.positionNodes(svgDim, nodes)
190 .then((nodes: IXosSgNode[]) => {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800191 this.loader = false;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800192
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800193 // NOTE keep the position for the nodes that already in the graph
194 nodes = _.map(nodes, (n: IXosSgNode) => {
195
196 if (n.id === 'undefined') {
197 // FIXME why the fabric ONOS app is not displayed and the VTN ONOS app is???
198 console.warn(n);
199 }
200
201 const previousVal = _.find(this.forceLayout.nodes(), {id: n.id});
202
203 if (previousVal) {
204 n.x = previousVal.x;
205 n.y = previousVal.y;
206 n.fixed = previousVal.fixed;
207 }
208 return n;
209 });
210
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800211 this.forceLayout
212 .nodes(nodes)
213 .links(links)
214 .size([svgDim.width, svgDim.height])
215 .linkDistance(config.force.linkDistance)
216 .charge(config.force.charge)
217 .gravity(config.force.gravity)
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800218 .linkStrength((link: IXosSgLink) => {
219 switch (link.type) {
220 case 'ownership':
221 case 'instance_ownership':
222 // NOTE make "ownsership" links stronger than other for positioning
223 return 1;
224 }
225 return 0.1;
226 })
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800227 .start();
228
229 // render nodes
230 this.XosNodeRenderer.renderNodes(this.forceLayout, this.nodeGroup, nodes);
231 this.renderLinks(links);
232 });
233 }
234
235 private renderLinks(links: any[]) {
236
237 const link = this.linkGroup
238 .selectAll('line')
239 .data(links, l => l.id);
240
241 const entering = link.enter();
242
243 entering.append('line')
244 .attr({
245 id: n => n.id,
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800246 'class': n => n.type
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800247 });
248
249 link.exit().remove();
250 }
251
252}
253
254export const XosServiceGraph: angular.IComponentOptions = {
255 template: require('./graph.component.html'),
256 controllerAs: 'vm',
257 controller: XosServiceGraphCtrl,
258};