2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 import * as _ from "lodash";
22 import {CommonNodeBase, CompositionCiLinkBase, RelationshipModel, Relationship, CompositionCiNodeBase, NodesFactory, LinksFactory} from "app/models";
23 import {GraphUIObjects} from "app/utils";
24 import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link";
25 import {Requirement, Capability} from "app/models";
27 * Created by obarda on 12/21/2016.
29 export class CommonGraphUtils {
31 constructor(private NodesFactory:NodesFactory, private LinksFactory:LinksFactory) {
35 public safeApply = (scope:ng.IScope, fn:any) => { //todo remove to general utils
36 let phase = scope.$root.$$phase;
37 if (phase == '$apply' || phase == '$digest') {
38 if (fn && (typeof(fn) === 'function')) {
47 * Draw node on the graph
49 * @param compositionGraphNode
51 * @returns {CollectionElements}
53 public addNodeToGraph(cy:Cy.Instance, compositionGraphNode:CommonNodeBase, position?:Cy.Position):Cy.CollectionElements {
55 let node = cy.add(<Cy.ElementDefinition> {
58 data: compositionGraphNode,
59 classes: compositionGraphNode.classes
62 if(!node.data().isUcpe) { //ucpe should not have tooltip
63 this.initNodeTooltip(node);
69 * The function will create a component instance node by the componentInstance position.
70 * If the node is UCPE the function will create all cp lan&wan for the ucpe
72 * @param compositionGraphNode
73 * @returns {Cy.CollectionElements}
75 public addComponentInstanceNodeToGraph(cy:Cy.Instance, compositionGraphNode:CompositionCiNodeBase):Cy.CollectionElements {
78 x: +compositionGraphNode.componentInstance.posX,
79 y: +compositionGraphNode.componentInstance.posY
82 let node = this.addNodeToGraph(cy, compositionGraphNode, nodePosition);
83 if (compositionGraphNode.isUcpe) {
84 this.createUcpeCpNodes(cy, node);
90 * This function will create CP_WAN & CP_LAN for the UCPE. this is a special node on the group that will behave like ports on the ucpe
92 * @param ucpeGraphNode
94 private createUcpeCpNodes(cy:Cy.Instance, ucpeGraphNode:Cy.CollectionNodes):void {
96 let requirementsArray:Array<any> = ucpeGraphNode.data().componentInstance.requirements["tosca.capabilities.Node"];
97 //show only LAN or WAN requirements
98 requirementsArray = _.reject(requirementsArray, (requirement:any) => {
99 let name:string = requirement.ownerName.toLowerCase();
100 return name.indexOf('lan') === -1 && name.indexOf('wan') === -1;
102 requirementsArray.sort(function (a, b) {
103 let nameA = a.ownerName.toLowerCase().match(/[^ ]+/)[0];
104 let nameB = b.ownerName.toLowerCase().match(/[^ ]+/)[0];
105 let numA = _.last(a.ownerName.toLowerCase().split(' '));
106 let numB = _.last(b.ownerName.toLowerCase().split(' '));
108 if (nameA === nameB) return numA > numB ? 1 : -1;
109 return nameA < nameB ? 1 : -1;
111 let position = angular.copy(ucpeGraphNode.boundingbox());
112 //add CP nodes to group
113 let topCps:number = 0;
114 for (let i = 0; i < requirementsArray.length; i++) {
116 let cpNode = this.NodesFactory.createUcpeCpNode(angular.copy(ucpeGraphNode.data().componentInstance));
117 cpNode.componentInstance.capabilities = requirementsArray[i];
118 cpNode.id = requirementsArray[i].ownerId;
119 cpNode.group = ucpeGraphNode.data().componentInstance.uniqueId;
120 cpNode.name = requirementsArray[i].ownerName; //for tooltip
121 cpNode.displayName = requirementsArray[i].ownerName;
122 cpNode.displayName = cpNode.displayName.length > 5 ? cpNode.displayName.substring(0, 5) + '...' : cpNode.displayName;
125 if (cpNode.name.toLowerCase().indexOf('lan') > -1) {
126 cpNode.textPosition = "top";
127 cpNode.componentInstance.posX = position.x1 + (i * 90) - (topCps * 90) + 53;
128 cpNode.componentInstance.posY = position.y1 + 400 + 27;
130 cpNode.textPosition = "bottom";
131 cpNode.componentInstance.posX = position.x1 + (topCps * 90) + 53;
132 cpNode.componentInstance.posY = position.y1 + 27;
135 let cyCpNode = this.addComponentInstanceNodeToGraph(cy, cpNode);
141 * Returns relation source and target nodes.
142 * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
145 * @returns [source, target] array of source node and target node.
147 public getRelationNodes(nodes:Cy.CollectionNodes, fromNodeId:string, toNodeId:string) {
149 _.find(nodes, (node:Cy.CollectionFirst) => node.data().id === fromNodeId),
150 _.find(nodes, (node:Cy.CollectionFirst) => node.data().id === toNodeId)
155 * Add link to graph - only draw the link
158 * @param getRelationRequirementCapability
160 public insertLinkToGraph = (cy:Cy.Instance, link:CompositionCiLinkBase, getRelationRequirementCapability:Function) => {
161 const relationNodes = this.getRelationNodes(cy.nodes(), link.source, link.target);
162 const sourceNode:CompositionCiNodeBase = relationNodes[0] && relationNodes[0].data();
163 const targetNode:CompositionCiNodeBase = relationNodes[1] && relationNodes[1].data();
164 if ((sourceNode && !sourceNode.certified) || (targetNode && !targetNode.certified)) {
165 link.classes = 'not-certified-link';
167 let linkElement = cy.add({
170 classes: link.classes
172 const getLinkRequirementCapability = () =>
173 getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance);
174 this.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability);
178 * Add service path link to graph - only draw the link
182 public insertServicePathLinkToGraph = (cy:Cy.Instance, link:CompositionCiServicePathLink) => {
183 let linkElement = cy.add({
186 classes: link.classes
188 this.initServicePathTooltip(linkElement, link);
192 * Returns function for the link tooltip content
193 * @param {Relationship} linkRelation
194 * @param {Requirement} requirement
195 * @param {Capability} capability
196 * @returns {() => string}
199 private _getLinkTooltipContent(linkRelation:Relationship, requirement?:Requirement, capability?:Capability):string {
200 return '<div class="line">' +
201 '<span class="req-cap-label">R: </span>' +
202 '<span>' + (requirement ? requirement.getTitle() : linkRelation.relation.requirement) + '</span>' +
204 '<div class="line">' +
205 '<div class="sprite-new link-tooltip-arrow"></div>' +
206 '<span class="req-cap-label">C: </span>' +
207 '<span>' + (capability ? capability.getTitle() : linkRelation.relation.capability) + '</span>' +
212 * This function will init qtip tooltip on the link
213 * @param linkElement - the link we want the tooltip to apply on,
215 * @param getLinkRequirementCapability
216 * link - the link obj
218 public initLinkTooltip(linkElement:Cy.CollectionElements, link:Relationship, getLinkRequirementCapability:Function) {
219 const content = () => this._getLinkTooltipContent(link); // base tooltip content without owner names
220 const render = (event, api) => {
221 // on render (called once at first show), get the link requirement and capability and change to full tooltip content (with owner names)
222 getLinkRequirementCapability().then((linkReqCap) => {
223 const fullContent = () => this._getLinkTooltipContent(link, linkReqCap.requirement, linkReqCap.capability);
224 api.set('content.text', fullContent);
227 linkElement.qtip(this.prepareInitTooltipData({content, events: {render}}));
235 public initServicePathTooltip(linkElement:Cy.CollectionElements, link:CompositionCiServicePathLink) {
236 let content = function () {
237 return '<div class="line">' +
238 '<div>'+link.pathName+'</div>' +
241 linkElement.qtip(this.prepareInitTooltipData({content}));
244 private prepareInitTooltipData(options?:Object) {
253 classes: 'qtip-dark qtip-rounded qtip-custom link-qtip',
263 hide: {event: 'mouseout mousedown'},
270 * go over the relations and draw links on the graph
272 * @param instancesRelations
273 * @param getRelationRequirementCapability - function to get requirement and capability of a relation
275 public initGraphLinks(cy:Cy.Instance, instancesRelations:Array<RelationshipModel>, getRelationRequirementCapability:Function) {
277 if (instancesRelations) {
278 _.forEach(instancesRelations, (relationshipModel:RelationshipModel) => {
279 _.forEach(relationshipModel.relationships, (relationship:Relationship) => {
280 let linkToCreate = this.LinksFactory.createGraphLink(cy, relationshipModel, relationship);
281 this.insertLinkToGraph(cy, linkToCreate, getRelationRequirementCapability);
288 * Determine which nodes are in the UCPE and set child data for them.
291 public initUcpeChildren(cy:Cy.Instance) {
292 let ucpe:Cy.CollectionNodes = cy.nodes('[?isUcpe]'); // Get ucpe on graph if exist
293 _.each(cy.edges('.ucpe-host-link'), (link)=> {
295 let ucpeChild:Cy.CollectionNodes = (link.source().id() == ucpe.id()) ? link.target() : link.source();
296 this.initUcpeChildData(ucpeChild, ucpe);
298 //vls dont have ucpe-host-link connection, so need to find them and iterate separately
299 let connectedVLs = ucpeChild.connectedEdges().connectedNodes('.vl-node');
300 _.forEach(connectedVLs, (vl)=> { //all connected vls must be UCPE children because not allowed to connect to a VL outside of the UCPE
301 this.initUcpeChildData(vl, ucpe);
307 * Set properties for nodes contained by the UCPE
308 * @param childNode- node contained in UCPE
309 * @param ucpe- ucpe container node
311 public initUcpeChildData(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) {
313 if (!childNode.data('isInsideGroup')) {
314 this.updateUcpeChildPosition(childNode, ucpe);
315 childNode.data({isInsideGroup: true});
321 * Updates UCPE child node offset, which allows child nodes to be dragged in synchronization with ucpe
322 * @param childNode- node contained in UCPE
323 * @param ucpe- ucpe container node
325 public updateUcpeChildPosition(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) {
326 let childPos:Cy.Position = childNode.relativePosition();
327 let ucpePos:Cy.Position = ucpe.relativePosition();
328 let offset:Cy.Position = {
329 x: childPos.x - ucpePos.x,
330 y: childPos.y - ucpePos.y
332 childNode.data("ucpeOffset", offset);
336 * Removes ucpe-child properties from the node
337 * @param childNode- node being removed from UCPE
339 public removeUcpeChildData(childNode:Cy.CollectionNodes) {
340 childNode.removeData("ucpeOffset");
341 childNode.data({isInsideGroup: false});
346 public HTMLCoordsToCytoscapeCoords(cytoscapeBoundingBox:Cy.Extent, mousePos:Cy.Position):Cy.Position {
347 return {x: mousePos.x + cytoscapeBoundingBox.x1, y: mousePos.y + cytoscapeBoundingBox.y1}
351 public getCytoscapeNodePosition = (cy:Cy.Instance, event:IDragDropEvent):Cy.Position => {
352 let targetOffset = $(event.target).offset();
353 let x = (event.pageX - targetOffset.left) / cy.zoom();
354 let y = (event.pageY - targetOffset.top) / cy.zoom();
356 return this.HTMLCoordsToCytoscapeCoords(cy.extent(), {
363 public getNodePosition(node:Cy.CollectionFirstNode):Cy.Position {
364 let nodePosition = node.relativePoint();
365 if (node.data().isUcpe) { //UCPEs use bounding box and not relative point.
366 nodePosition = {x: node.boundingbox().x1, y: node.boundingbox().y1};
373 * Generic function that can be used for any html elements overlaid on canvas
374 * Returns the html position of a node on canvas, including left palette and header offsets. Option to pass in additional offset to add to return position.
376 * @param additionalOffset
377 * @returns {Cy.Position}
379 public getNodePositionWithOffset = (node:Cy.CollectionFirstNode, additionalOffset?:Cy.Position): Cy.Position => {
380 if(!additionalOffset) additionalOffset = {x: 0, y:0};
382 let nodePosition = node.renderedPosition();
383 let posWithOffset:Cy.Position = {
384 x: nodePosition.x + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET + additionalOffset.x,
385 y: nodePosition.y + GraphUIObjects.COMPOSITION_HEADER_OFFSET + additionalOffset.y
387 return posWithOffset;
391 * return true/false if first node contains in second - this used in order to verify is node is entirely inside ucpe
396 public isFirstBoxContainsInSecondBox(firstBox:Cy.BoundingBox, secondBox:Cy.BoundingBox) {
398 return firstBox.x1 > secondBox.x1 && firstBox.x2 < secondBox.x2 && firstBox.y1 > secondBox.y1 && firstBox.y2 < secondBox.y2;
404 * Check if node node bounds position is inside any ucpe on graph, and return the ucpe
405 * @param {diagram} the diagram.
406 * @param {nodeActualBounds} the actual bound position of the node.
407 * @return the ucpe if found else return null
409 public isInUcpe = (cy:Cy.Instance, nodeBounds:Cy.BoundingBox):Cy.CollectionElements => {
411 let ucpeNodes = cy.nodes('[?isUcpe]').filterFn((ucpeNode) => {
412 return this.isFirstBoxContainsInSecondBox(nodeBounds, ucpeNode.boundingbox());
423 public getLinkableNodes(cy:Cy.Instance, node:Cy.CollectionFirstNode):Array<CompositionCiNodeBase> {
424 let compatibleNodes = [];
425 _.each(cy.nodes(), (tempNode)=> {
426 if (this.nodeLocationsCompatible(cy, node, tempNode)) {
427 compatibleNodes.push(tempNode.data());
430 return compatibleNodes;
434 * Checks whether node locations are compatible in reference to UCPEs.
435 * Returns true if both nodes are in UCPE or both nodes out, or one node is UCPEpart.
439 public nodeLocationsCompatible(cy:Cy.Instance, node1:Cy.CollectionFirstNode, node2:Cy.CollectionFirstNode) {
441 let ucpe = cy.nodes('[?isUcpe]');
442 if(!ucpe.length){ return true; }
443 if(node1.data().isUcpePart || node2.data().isUcpePart) { return true; }
445 return (this.isFirstBoxContainsInSecondBox(node1.boundingbox(), ucpe.boundingbox()) == this.isFirstBoxContainsInSecondBox(node2.boundingbox(), ucpe.boundingbox()));
450 * This function will init qtip tooltip on the node
451 * @param node - the node we want the tooltip to apply on
453 public initNodeTooltip(node:Cy.CollectionNodes) {
456 content: function () {
457 return this.data('name');
465 classes: 'qtip-dark qtip-rounded qtip-custom',
475 hide: {event: 'mouseout mousedown'},
479 if (node.data().isUcpePart){ //fix tooltip positioning for UCPE-cps
480 opts.position.adjust = {x:0, y:20};
487 CommonGraphUtils.$inject = ['NodesFactory', 'LinksFactory'];