/** * Created by obarda on 12/21/2016. */ /** * Created by obarda on 12/13/2016. */ /// module Sdc.Graph.Utils { export class CommonGraphUtils { constructor(private NodesFactory:Sdc.Utils.NodesFactory, private LinksFactory:Sdc.Utils.LinksFactory) { } public safeApply = (scope:ng.IScope, fn:any) => { //todo remove to general utils let phase = scope.$root.$$phase; if (phase == '$apply' || phase == '$digest') { if (fn && (typeof(fn) === 'function')) { fn(); } } else { scope.$apply(fn); } }; /** * Draw node on the graph * @param cy * @param compositionGraphNode * @param position * @returns {CollectionElements} */ public addNodeToGraph(cy:Cy.Instance, compositionGraphNode:Models.Graph.CommonNodeBase, position?:Cy.Position):Cy.CollectionElements { var node = cy.add( { group: 'nodes', position: position, data: compositionGraphNode, classes: compositionGraphNode.classes }); if(!node.data().isUcpe) { //ucpe should not have tooltip this.initNodeTooltip(node); } return node; }; /** * The function will create a component instance node by the componentInstance position. * If the node is UCPE the function will create all cp lan&wan for the ucpe * @param cy * @param compositionGraphNode * @returns {Cy.CollectionElements} */ public addComponentInstanceNodeToGraph(cy:Cy.Instance, compositionGraphNode:Models.Graph.CompositionCiNodeBase):Cy.CollectionElements { let nodePosition = { x: +compositionGraphNode.componentInstance.posX, y: +compositionGraphNode.componentInstance.posY }; let node = this.addNodeToGraph(cy, compositionGraphNode, nodePosition); if (compositionGraphNode.isUcpe) { this.createUcpeCpNodes(cy, node); } return node; }; /** * 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 * @param cy * @param ucpeGraphNode */ private createUcpeCpNodes(cy:Cy.Instance, ucpeGraphNode:Cy.CollectionNodes):void { let requirementsArray:Array = ucpeGraphNode.data().componentInstance.requirements["tosca.capabilities.Node"]; //show only LAN or WAN requirements requirementsArray = _.reject(requirementsArray, (requirement:any) => { let name:string = requirement.ownerName.toLowerCase(); return name.indexOf('lan') === -1 && name.indexOf('wan') === -1; }); requirementsArray.sort(function (a, b) { let nameA = a.ownerName.toLowerCase().match(/[^ ]+/)[0]; let nameB = b.ownerName.toLowerCase().match(/[^ ]+/)[0]; let numA = _.last(a.ownerName.toLowerCase().split(' ')); let numB = _.last(b.ownerName.toLowerCase().split(' ')); if (nameA === nameB) return numA > numB ? 1 : -1; return nameA < nameB ? 1 : -1; }); let position = angular.copy(ucpeGraphNode.boundingbox()); //add CP nodes to group let topCps:number = 0; for (let i = 0; i < requirementsArray.length; i++) { let cpNode = this.NodesFactory.createUcpeCpNode(angular.copy(ucpeGraphNode.data().componentInstance)); cpNode.componentInstance.capabilities = requirementsArray[i]; cpNode.id = requirementsArray[i].ownerId; cpNode.group = ucpeGraphNode.data().componentInstance.uniqueId; cpNode.name = requirementsArray[i].ownerName; //for tooltip cpNode.displayName = requirementsArray[i].ownerName; cpNode.displayName = cpNode.displayName.length > 5 ? cpNode.displayName.substring(0, 5) + '...' : cpNode.displayName; if (cpNode.name.toLowerCase().indexOf('lan') > -1) { cpNode.textPosition = "top"; cpNode.componentInstance.posX = position.x1 + (i * 90) - (topCps * 90) + 53; cpNode.componentInstance.posY = position.y1 + 400 + 27; } else { cpNode.textPosition = "bottom"; cpNode.componentInstance.posX = position.x1 + (topCps * 90) + 53; cpNode.componentInstance.posY = position.y1 + 27; topCps++; } let cyCpNode = this.addComponentInstanceNodeToGraph(cy, cpNode); cyCpNode.lock(); } }; /** * * @param nodes - all nodes in graph in order to find the edge connecting the two nodes * @param fromNodeId * @param toNodeId * @returns {boolean} true/false if the edge is certified (from node and to node are certified) */ public isRelationCertified(nodes:Cy.CollectionNodes, fromNodeId:string, toNodeId:string):boolean { let resourceTemp = _.filter(nodes, function (node:Cy.CollectionFirst) { return node.data().id === fromNodeId || node.data().id === toNodeId; }); let certified:boolean = true; _.forEach(resourceTemp, (item) => { certified = certified && item.data().certified; }); return certified; } /** * Add link to graph - only draw the link * @param cy * @param link */ public insertLinkToGraph = (cy:Cy.Instance, link:Models.CompositionCiLinkBase) => { if (!this.isRelationCertified(cy.nodes(), link.source, link.target)) { link.classes = 'not-certified-link'; } cy.add({ group: 'edges', data: link, classes: link.classes }); }; /** * go over the relations and draw links on the graph * @param cy * @param instancesRelations */ public initGraphLinks(cy:Cy.Instance, instancesRelations:Array) { if (instancesRelations) { _.forEach(instancesRelations, (relationshipModel:Models.RelationshipModel) => { _.forEach(relationshipModel.relationships, (relationship:Models.Relationship) => { let linkToCreate = this.LinksFactory.createGraphLink(cy, relationshipModel, relationship); this.insertLinkToGraph(cy, linkToCreate); }); }); } } /** * Determine which nodes are in the UCPE and set child data for them. * @param cy */ public initUcpeChildren(cy:Cy.Instance){ let ucpe:Cy.CollectionNodes = cy.nodes('[?isUcpe]'); // Get ucpe on graph if exist _.each(cy.edges('.ucpe-host-link'), (link)=>{ let ucpeChild:Cy.CollectionNodes = (link.source().id() == ucpe.id())? link.target() : link.source(); this.initUcpeChildData(ucpeChild, ucpe); //vls dont have ucpe-host-link connection, so need to find them and iterate separately let connectedVLs = ucpeChild.connectedEdges().connectedNodes('.vl-node'); _.forEach(connectedVLs, (vl)=>{ //all connected vls must be UCPE children because not allowed to connect to a VL outside of the UCPE this.initUcpeChildData(vl, ucpe); }); }); } /** * Set properties for nodes contained by the UCPE * @param childNode- node contained in UCPE * @param ucpe- ucpe container node */ public initUcpeChildData(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes){ if(!childNode.data('isInsideGroup')){ this.updateUcpeChildPosition(childNode, ucpe); childNode.data({isInsideGroup: true}); } } /** * Updates UCPE child node offset, which allows child nodes to be dragged in synchronization with ucpe * @param childNode- node contained in UCPE * @param ucpe- ucpe container node */ public updateUcpeChildPosition(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes){ let childPos:Cy.Position = childNode.relativePosition(); let ucpePos:Cy.Position = ucpe.relativePosition(); let offset:Cy.Position = { x: childPos.x - ucpePos.x, y: childPos.y - ucpePos.y }; childNode.data("ucpeOffset", offset); } /** * Removes ucpe-child properties from the node * @param childNode- node being removed from UCPE */ public removeUcpeChildData(childNode:Cy.CollectionNodes){ childNode.removeData("ucpeOffset"); childNode.data({isInsideGroup: false}); } public HTMLCoordsToCytoscapeCoords(cytoscapeBoundingBox:Cy.Extent, mousePos:Cy.Position):Cy.Position { return {x: mousePos.x + cytoscapeBoundingBox.x1, y: mousePos.y + cytoscapeBoundingBox.y1} }; public getCytoscapeNodePosition = (cy: Cy.Instance, event:IDragDropEvent):Cy.Position => { let targetOffset = $(event.target).offset(); let x = event.pageX - targetOffset.left; let y = event.pageY - targetOffset.top; return this.HTMLCoordsToCytoscapeCoords(cy.extent(), { x: x, y: y }); }; public getNodePosition(node:Cy.CollectionFirstNode):Cy.Position{ let nodePosition = node.relativePoint(); if(node.data().isUcpe){ //UCPEs use bounding box and not relative point. nodePosition = {x: node.boundingbox().x1, y: node.boundingbox().y1}; } return nodePosition; } /** * return true/false if first node contains in second - this used in order to verify is node is entirely inside ucpe * @param firstBox * @param secondBox * @returns {boolean} */ public isFirstBoxContainsInSecondBox(firstBox:Cy.BoundingBox, secondBox:Cy.BoundingBox) { return firstBox.x1 > secondBox.x1 && firstBox.x2 < secondBox.x2 && firstBox.y1 > secondBox.y1 && firstBox.y2 < secondBox.y2; }; /** * Check if node node bounds position is inside any ucpe on graph, and return the ucpe * @param {diagram} the diagram. * @param {nodeActualBounds} the actual bound position of the node. * @return the ucpe if found else return null */ public isInUcpe = (cy: Cy.Instance, nodeBounds: Cy.BoundingBox): Cy.CollectionElements => { let ucpeNodes = cy.nodes('[?isUcpe]').filterFn((ucpeNode) => { return this.isFirstBoxContainsInSecondBox(nodeBounds, ucpeNode.boundingbox()); }); return ucpeNodes; }; /** * * @param cy * @param node * @returns {Array} */ public getLinkableNodes(cy:Cy.Instance, node:Cy.CollectionFirstNode):Array{ let compatibleNodes = []; _.each(cy.nodes(), (tempNode)=>{ if(this.nodeLocationsCompatible(cy, node, tempNode)){ compatibleNodes.push(tempNode.data()); } }); return compatibleNodes; } /** * Checks whether node locations are compatible in reference to UCPEs. * Returns true if both nodes are in UCPE or both nodes out, or one node is UCPEpart. * @param node1 * @param node2 */ public nodeLocationsCompatible(cy:Cy.Instance, node1:Cy.CollectionFirstNode, node2:Cy.CollectionFirstNode){ let ucpe = cy.nodes('[?isUcpe]'); if(!ucpe.length){ return true; } if(node1.data().isUcpePart || node2.data().isUcpePart) { return true; } return (this.isFirstBoxContainsInSecondBox(node1.boundingbox(), ucpe.boundingbox()) == this.isFirstBoxContainsInSecondBox(node2.boundingbox(), ucpe.boundingbox())); } /** * This function will init qtip tooltip on the node * @param node - the node we want the tooltip to apply on */ public initNodeTooltip(node:Cy.CollectionNodes) { let opts = { content: function () { return this.data('name'); }, position: { my: 'top center', at: 'bottom center', adjust: {x:0, y:-5} }, style: { classes: 'qtip-dark qtip-rounded qtip-custom', tip: { width: 16, height: 8 } }, show: { event: 'mouseover', delay: 1000 }, hide: {event: 'mouseout mousedown'}, includeLabels: true }; if (node.data().isUcpePart){ //fix tooltip positioning for UCPE-cps opts.position.adjust = {x:0, y:20}; } node.qtip(opts); }; }; CommonGraphUtils.$inject = ['NodesFactory', 'LinksFactory']; }