blob: b0cabb9881986206332fb1e1989bb10a7c77d0f3 [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';
Matteo Scandolob8cdf552018-02-12 17:56:26 -080031import {GraphStates, IXosGraphStateMachine} from '../../services/graph-state-machine';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080032
33class XosServiceGraphCtrl {
34 static $inject = [
35 '$log',
36 '$scope',
37 'XosGraphStore',
38 'XosGraphHelpers',
39 'XosServiceGraphIcons',
40 'XosNodePositioner',
41 'XosNodeRenderer',
Matteo Scandolob8cdf552018-02-12 17:56:26 -080042 'XosGraphConfig',
43 'XosGraphStateMachine'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080044 ];
45
Matteo Scandolob8cdf552018-02-12 17:56:26 -080046 // graph status
47 public currentState: number;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080048 public loader: boolean = true;
49
50 private GraphSubscription: Subscription;
51 private graph: any; // this is the Graph instance
52
Matteo Scandolob8cdf552018-02-12 17:56:26 -080053
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080054 // graph element
55 private svg;
56 private linkGroup;
57 private nodeGroup;
58 private forceLayout;
59
60 constructor (
61 private $log: ng.ILogService,
62 private $scope: ng.IScope,
63 private XosGraphStore: IXosGraphStore,
64 private XosGraphHelpers: IXosGraphHelpers,
65 private XosServiceGraphIcons: IXosServiceGraphIcons,
66 private XosNodePositioner: IXosNodePositioner,
67 private XosNodeRenderer: IXosNodeRenderer,
Matteo Scandolob8cdf552018-02-12 17:56:26 -080068 private XosGraphConfig: IXosGraphConfig,
69 public XosGraphStateMachine: IXosGraphStateMachine
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080070 ) {
71 this.$log.info('[XosServiceGraph] Component setup');
72
Matteo Scandolob8cdf552018-02-12 17:56:26 -080073 this.currentState = this.XosGraphStateMachine.getCurrentState();
74
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080075 this.XosGraphConfig.setupKeyboardShortcuts();
76
77 this.setupSvg();
78 this.setupForceLayout();
79
80 this.GraphSubscription = this.XosGraphStore.get()
81 .subscribe(
82 graph => {
83 this.graph = graph;
84 if (this.graph.nodes().length > 0) {
Matteo Scandolob8cdf552018-02-12 17:56:26 -080085 this.$log.info('[XosServiceGraph] Rendering graph: ', this.graph.nodes(), this.graph.edges());
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080086 this.renderGraph(this.graph);
87 }
88 },
89 error => {
90 this.$log.error('[XosServiceGraph] XosGraphStore observable error: ', error);
91 }
92 );
93
94 this.$scope.$on('xos.sg.update', () => {
95 this.$log.info(`[XosServiceGraph] Received event: xos.sg.update`);
96 this.renderGraph(this.graph);
97 });
Matteo Scandolo35fdf242017-11-30 12:29:45 -080098
Matteo Scandolob8cdf552018-02-12 17:56:26 -080099 this.$scope.$on('xos.sg.stateChange', (event, state: number) => {
100 this.$log.info(`[XosServiceGraph] Received event: xos.sg.stateChange. New state is ${state}`);
101 this.$scope.$applyAsync(() => {
102 this.currentState = state;
103 });
104 });
105
Matteo Scandolo35fdf242017-11-30 12:29:45 -0800106 $(window).resize(() => {
107 this.renderGraph(this.graph);
108 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800109 }
110
111 $onDestroy() {
112 this.GraphSubscription.unsubscribe();
113 }
114
115 public closeFullscreen() {
116 this.XosGraphConfig.toggleFullscreen();
117 }
118
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800119 public toggleModel(modelName: string): void {
120 switch (modelName) {
121 case 'services':
122 this.XosGraphStateMachine.go(GraphStates.Services);
123 break;
124 case 'serviceinstances':
125 this.XosGraphStateMachine.go(GraphStates.ServiceInstances);
126 break;
127 case 'instances':
128 this.XosGraphStateMachine.go(GraphStates.Instances);
129 break;
130 case 'networks':
131 this.XosGraphStateMachine.go(GraphStates.Networks);
132 break;
133 }
134 }
135
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800136 private setupSvg() {
137 this.svg = d3.select('xos-service-graph svg');
138
139 this.linkGroup = this.svg.append('g')
140 .attr({
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800141 'class': 'link-group'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800142 });
143
144 this.nodeGroup = this.svg.append('g')
145 .attr({
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800146 'class': 'node-group'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800147 });
148 }
149
150 private setupForceLayout() {
151 this.$log.debug(`[XosServiceGraph] Setup Force Layout`);
152 const tick = () => {
153 this.nodeGroup.selectAll('g.node')
154 .attr({
155 transform: d => `translate(${d.x}, ${d.y})`
156 });
157
158 this.linkGroup.selectAll('line')
159 .attr({
160 x1: l => l.source.x || 0,
161 y1: l => l.source.y || 0,
162 x2: l => l.target.x || 0,
163 y2: l => l.target.y || 0,
164 });
165 };
166
167 const svgDim = this.getSvgDimensions();
168
169 this.forceLayout =
170 d3.layout.force()
171 .size([svgDim.width, svgDim.height])
172 .on('tick', tick);
173 }
174
175 private getSvgDimensions(): {width: number, height: number} {
176 return {
177 width: $('xos-service-graph svg').width(),
178 height: $('xos-service-graph svg').height()
179 };
180 }
181
182 private renderGraph(graph: any) {
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800183
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800184 let nodes: IXosSgNode[] = this.XosGraphStore.nodesFromGraph(graph);
185 let links = this.XosGraphStore.linksFromGraph(graph);
186 const svgDim = this.getSvgDimensions();
187
188 this.XosNodePositioner.positionNodes(svgDim, nodes)
189 .then((nodes: IXosSgNode[]) => {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800190 this.loader = false;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800191
192 this.forceLayout
193 .nodes(nodes)
194 .links(links)
195 .size([svgDim.width, svgDim.height])
196 .linkDistance(config.force.linkDistance)
197 .charge(config.force.charge)
198 .gravity(config.force.gravity)
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800199 .linkStrength((link: IXosSgLink) => {
200 switch (link.type) {
201 case 'ownership':
202 case 'instance_ownership':
203 // NOTE make "ownsership" links stronger than other for positioning
204 return 1;
205 }
206 return 0.1;
207 })
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800208 .start();
209
210 // render nodes
211 this.XosNodeRenderer.renderNodes(this.forceLayout, this.nodeGroup, nodes);
212 this.renderLinks(links);
213 });
214 }
215
216 private renderLinks(links: any[]) {
217
218 const link = this.linkGroup
219 .selectAll('line')
220 .data(links, l => l.id);
221
222 const entering = link.enter();
223
224 entering.append('line')
225 .attr({
226 id: n => n.id,
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800227 'class': n => n.type
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800228 });
229
230 link.exit().remove();
231 }
232
233}
234
235export const XosServiceGraph: angular.IComponentOptions = {
236 template: require('./graph.component.html'),
237 controllerAs: 'vm',
238 controller: XosServiceGraphCtrl,
239};