/** * It manages all the Connectors on a diagram * * @constructor * @this {ConnectorManager} **/ function ConnectorManager(){ /**An {Array} of {Connector}s. Keeps all Connectors from canvas*/ this.connectors = []; /**An {Array} of {ConnectionPoint}s. Keeps all ConnectionPoints from canvas*/ this.connectionPoints = []; /**Used to generate unique IDs for ConnectionPoint*/ this.connectionPointCurrentId = 0; // /**An {Array} of {Glue}s. Keeps all Glues from canvas*/ this.glues = []; /** Tells in what mode are we: * 0 = disabled * 1 = choosing first location (creation) * 2 = choosing second location (creation) * 3 = dragging connector */ this.connectionMode = ConnectorManager.MODE_DISABLED; } // defines current cloud's paint details ConnectorManager.CLOUD_RADIUS = 12; ConnectorManager.CLOUD_LINEWIDTH = 3; ConnectorManager.CLOUD_STROKE_STYLE = "rgba(255, 153, 0, 0.8)"; //orange /**Creates a {ConnectorManager} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {ConnectorManager} a newly constructed ConnectorManager *@author Alex Gheorghiu **/ ConnectorManager.load = function(o){ var newConnectionManager = new ConnectorManager(); //empty constructor var localLog = ''; //1 - load connectors localLog += '\nCONNECTORS'; newConnectionManager.connectors = Connector.loadArray(o.connectors); // newConnectionManager.connectorSelectedIndex = o.connectorSelectedIndex; for(var i=0;i **/ equals : function(anotherConnectionManager){ if(!anotherConnectionManager instanceof ConnectorManager){ return false; } //test Connectors for(var i=0; i **/ connectorGetByTextXY: function(x, y) { var connectorsLength = this.connectors.length; for(var i = connectorsLength - 1; i >= 0; i--){ var connector = this.connectors[i]; if( connector.middleText.contains(x, y) ) { return connector.id; } }//end for return -1; }, /**Adjust a Connector by its (one) ConnectionPoint *@param {Integer} cpId - the id of the connector *@param {Number} x - the x coordinates of the point *@param {Number} y - the y coordinates of the point *@deprecated This function seems to no longer be used. *See ConnectionMamager.connectionPointTransform(...) **/ connectorAdjustByConnectionPoint: function(cpId, x, y){ var cp = this.connectionPointGetById(cpId); //ConnectionPoint Log.debug("connectorAdjustByConnectionPoint() - Cp is :" + cp); var con = this.connectorGetById(cp.parentId); //Connector var conCps = this.connectionPointGetAllByParent(con.id); //Conector's ConnectionPoints if(con.type === Connector.TYPE_STRAIGHT){ /**For STRAIGHT is very simple just update the tunrning points to the start and end connection points*/ var start = conCps[0].point.clone(); var end = conCps[1].point.clone(); con.turningPoints = [start, end]; } else if(con.type === Connector.TYPE_JAGGED || con.type === Connector.TYPE_ORGANIC){ //first ConnectionPoint var startPoint = conCps[0].point.clone(); //second ConnectionPoint var endPoint = conCps[1].point.clone(); //first bounds (of start figure) var sFigure = STACK.figureGetAsFirstFigureForConnector(con.id); var sBounds = sFigure == null ? null : sFigure.getBounds(); //second bounds (of end figure) var eFigure = STACK.figureGetAsSecondFigureForConnector(con.id); var eBounds = eFigure == null ? null : eFigure.getBounds(); //adjust connector var solutions = this.connector2Points(Connector.TYPE_JAGGED, startPoint, endPoint, sBounds, eBounds); // apply solution to Connector (for delta changes made by user) con.applySolution(solutions); conCps[0].point = con.turningPoints[0].clone(); conCps[1].point = con.turningPoints[con.turningPoints.length - 1].clone(); } }, /**Simple function to get access to first {ConnectionPoint} *@param {Number} connectorId - the id of the connector *@return {ConnectionPoint} - the start {ConnectionPoint} *@author Alex Gheorghiu **/ connectionPointGetFirstForConnector : function(connectorId) { return this.connectionPointGetAllByParentIdAndType(connectorId, ConnectionPoint.TYPE_CONNECTOR)[0]; }, /**Simple function to get access to second {ConnectionPoint} *@param {Number} connectorId - the id of the connector *@return {ConnectionPoint} - the end {ConnectionPoint} *@author Alex Gheorghiu **/ connectionPointGetSecondForConnector : function(connectorId) { return this.connectionPointGetAllByParentIdAndType(connectorId, ConnectionPoint.TYPE_CONNECTOR)[1]; }, /** * This function simply takes 2 points: M, N from 2 figures A, B (M belongs to A, * N belongs to B) and tries to find the * nearest {ConnectionPoint}s to M ( C1i ) and nearest {ConnectionPoint}s to N ( C2i ) and then * pick those that have a minimum distance between them (minimum C1-C2 distance] * *@param {Boolean} startAutomatic - flag defines if start of connection is automatic *@param {Boolean} endAutomatic - flag defines if end of connection is automatic *@param {Number} startFigureId - Start {Figure}'s id or -1 if no start {Figure} present *@param {Point} startPoint - Start {Point} of connection *@param {Number} endFigureId - End {Figure}'s id or -1 if no start {Figure} present *@param {Point} endPoint - End {Point} of connection * *@retun {Array} - in a form of * [ * startPoint, * endPoint, * start {Figure}'s {ConnectionPoint} Id - or -1 if none, * end {Figure}'s {ConnectionPoint}'s Id - or -1 if none * ] *@author Artyom Pokatilov **/ getClosestPointsOfConnection: function(startAutomatic, endAutomatic, startFigureId, startPoint, endFigureId, endPoint) { //We will have 4 cases depending on automaticStart and automaticEnd values //Case 1 // both points have locked position // we are taking defined {Point}s of connection if (!startAutomatic && !endAutomatic) { //TODO: not fair not to return CPs'ids where we know for sure they //are present( !automaticStart && !automaticEnd) return this.closestPointsFixed2Fixed(startPoint, endPoint); } //Case 2 // end point has locked position: // we are taking 1 point as position of closest {ConnectionPoint} connected with startPoint // and 2 point as end {ConnectionPoint} if (startAutomatic && !endAutomatic) { return this.closestPointsAuto2Fixed(startFigureId, endPoint); } //Case 3 // start point has locked position: // we taking 1 point as clone of {ConnectionPoint} // and 2 point as position of closest {ConnectionPoint} connected with end point if (!startAutomatic && endAutomatic) { return this.closestPointsFixed2Auto(startPoint, endFigureId); } //Case 4 // start and end points have locked position: // we taking both as position of closest {ConnectionPoint} connected with start and end point if (startAutomatic && endAutomatic) { return this.closestPointsAuto2Auto(startFigureId, startPoint, endFigureId, endPoint); } }, /** * Special case of getClosestPointsOfConnection() when * startAutomatic = true * and * endAutomatic = true * */ closestPointsAuto2Auto : function(startFigureId, startPoint, endFigureId, endPoint){ // special case when figure is connected to itself if (startFigureId == endFigureId) { // we will find closest to end point (mouse coordinates) // this means solutions will be the same as connecting to end point // from automatic start where start and end connection point match var candidate = this.closestPointsAuto2Fixed( startFigureId, //start figure's id startPoint, //start point endFigureId, //end figure's id endPoint //end ); candidate[1] = candidate[0]; candidate[3] = candidate[2]; return candidate; } //get all connection points of start figure var startFCps = this.connectionPointGetAllByParent(startFigureId), startFCpLength = startFCps.length, curStartPoint, closestStartPoint = startFCps[0].point, closestStartConnectionPointId = startFCps[0].id, endFCps = this.connectionPointGetAllByParent(endFigureId), endFCpLength = endFCps.length, curEndPoint, closestEndPoint = endFCps[0].point, closestEndConnectionPointId = endFCps[0].id, minDistance = Util.distance(closestStartPoint,closestEndPoint), curDistance; // find closest to endPoint for(var i = 0; i < startFCpLength; i++){ curStartPoint = startFCps[i].point; for(var j = 0; j < endFCpLength; j++){ curEndPoint = endFCps[j].point; curDistance = Util.distance(curStartPoint, curEndPoint); if (curDistance < minDistance) { minDistance = curDistance; closestStartPoint = curStartPoint; closestStartConnectionPointId = startFCps[i].id; closestEndPoint = curEndPoint; closestEndConnectionPointId = endFCps[j].id; } } } return [closestStartPoint.clone(), closestEndPoint.clone(), closestStartConnectionPointId, closestEndConnectionPointId]; }, /** * Special case of getClosestPointsOfConnection() when * startAutomatic = false * and * endAutomatic = true * */ closestPointsFixed2Auto : function(startPoint, endFigureId){ // //get all connection points of end figure var fCps = this.connectionPointGetAllByParent(endFigureId), fCpLength = fCps.length, closestPoint = fCps[0].point, closestConnectionPointId = fCps[0].id, minDistance = Util.distance(startPoint, closestPoint), curPoint, curDistance; // find closest to startPoint for(var i = 1; i < fCpLength; i++){ curPoint = fCps[i].point; curDistance = Util.distance(startPoint, curPoint); if (curDistance < minDistance) { minDistance = curDistance; closestPoint = curPoint; closestConnectionPointId = fCps[i].id; } } return [startPoint, closestPoint.clone(), -1, closestConnectionPointId]; }, /** * Special case of getClosestPointsOfConnection() when * startAutomatic = true * and * endAutomatic = false * */ closestPointsAuto2Fixed: function(startFigureId, endPoint){ //get all connection points of start figure var fCps = this.connectionPointGetAllByParent(startFigureId), fCpLength = fCps.length, closestPoint = fCps[0].point, closestConnectionPointId = fCps[0].id, minDistance = Util.distance(closestPoint, endPoint), curPoint, curDistance; // find closest to endPoint for(var i = 1; i < fCpLength; i++){ curPoint = fCps[i].point; curDistance = Util.distance(curPoint, endPoint); if (curDistance < minDistance) { minDistance = curDistance; closestPoint = curPoint; closestConnectionPointId = fCps[i].id; } } return [closestPoint.clone(), endPoint, closestConnectionPointId, -1]; }, /** * Special case of getClosestPointsOfConnection() when * startAutomatic = false * and * endAutomatic = false * */ closestPointsFixed2Fixed : function (startPoint, endPoint){ return [startPoint, endPoint, -1, -1]; }, /**This function returns a "temp" connector between 2 points. The points * are usually inside some boundaries and we need to "discover" a path * from start point to the end point. * * Note: We are not using {ConnectionPoint}s here are this somehow a generic algorithm * where we have a type of drawing, a start point, and end point and 2 boundaries to * consider. * * Note 2: For organic connector the solution(s) is "injected" with 2 additional * points: in the middle of start and end segment (of turning points) so that * the curve will look more natural. * *@param {Number} type - Connector.TYPE_STRAIGHT, Connector.TYPE_JAGGED or Connector.TYPE_ORGANIC *@param {Point} startPoint - the start {Point} *@param {Point} endPoint - the end {Point} *@param {Array} sBounds - the starting bounds (of a Figure) as [left, top, right, bottom] - area we should avoid *@param {Array} eBounds - the ending bounds (of a Figure) as [left, top, right, bottom] - area we shoudl avoid * *@return {Array} solution - in a form ('generic solution name', 'specific solution name', [point1, point2, ...]) *Example: ['s1', 's1_1', [point1, point2, point3, ...]] where 's1 - is the generic solution name, * s1_1 - the specific solution name (Case 1 of Solution 1) *@author Alex Gheorghiu **/ connector2Points: function(type, startPoint, endPoint, sBounds, eBounds ){ var oldLogLevel = Log.level; // Log.level = Log.LOG_LEVEL_DEBUG; Log.group("connectionManager: connector2Points"); Log.info("ConnectionManager: connector2Points (" + type + ", " + startPoint + ", " + endPoint + ", " + sBounds + ", " + eBounds + ')'); var solutions = []; switch(type){ case Connector.TYPE_STRAIGHT: var points = [startPoint.clone(), endPoint.clone()]; solutions.push( ['straight', 'straight', points] ); break; case Connector.TYPE_ORGANIC: //do nothing....just flow with JAGGED...for now case Connector.TYPE_JAGGED: var startExitPoint = null; var endExitPoint = null; //find start exit point if(sBounds != null){ var potentialExits = []; potentialExits.push(new Point(startPoint.x, sBounds[1] - FIGURE_ESCAPE_DISTANCE)); //north potentialExits.push(new Point(sBounds[2] + FIGURE_ESCAPE_DISTANCE, startPoint.y)); //east potentialExits.push(new Point(startPoint.x, sBounds[3] + FIGURE_ESCAPE_DISTANCE)); //south potentialExits.push(new Point(sBounds[0] - FIGURE_ESCAPE_DISTANCE, startPoint.y)); //west //pick closest exit point startExitPoint = potentialExits[0]; for(var i=1; i < potentialExits.length; i++){ if(Util.distance(startPoint, potentialExits[i]) < Util.distance(startPoint, startExitPoint)){ startExitPoint = potentialExits[i]; } } if(startExitPoint == null){ alert("No way"); } } //find end exit point if(eBounds != null){ var potentialExits = []; potentialExits.push(new Point(endPoint.x, eBounds[1] - FIGURE_ESCAPE_DISTANCE)); //north potentialExits.push(new Point(eBounds[2] + FIGURE_ESCAPE_DISTANCE, endPoint.y)); //east potentialExits.push(new Point(endPoint.x, eBounds[3] + FIGURE_ESCAPE_DISTANCE)); //south potentialExits.push(new Point(eBounds[0] - FIGURE_ESCAPE_DISTANCE, endPoint.y)); //west //pick closest exit point endExitPoint = potentialExits[0]; for(var i=1; i < potentialExits.length; i++){ if(Util.distance(endPoint, potentialExits[i]) < Util.distance(endPoint, endExitPoint)){ endExitPoint = potentialExits[i]; } } if(endExitPoint == null){ alert("No way"); } } //Basic solution (basic kit :p) var s = [startPoint]; var gapIndex = 0; //the index of the gap (where do we need to insert new points) DO NOT CHANGE IT if(startExitPoint){ s.push(startExitPoint); gapIndex = 1; } if(endExitPoint){ s.push(endExitPoint); } s.push(endPoint); //SO - no additional points var s0 = Point.cloneArray(s); solutions.push(['s0', 's0', s0]); //S1 var s1 = Point.cloneArray(s); //first variant var s1_1 = Point.cloneArray(s1); s1_1.splice(gapIndex + 1, 0, new Point(s1_1[gapIndex].x , s1_1[gapIndex+1].y) ); solutions.push(['s1', 's1_1', s1_1]); //second variant var s1_2 = Point.cloneArray(s1); s1_2.splice(gapIndex + 1, 0, new Point(s1_2[gapIndex+1].x , s1_2[gapIndex].y) ); solutions.push(['s1', 's1_2', s1_2]); //S2 //Variant I var s2_1 = Point.cloneArray(s); var s2_1_1 = new Point( (s2_1[gapIndex].x + s2_1[gapIndex+1].x) / 2, s2_1[gapIndex].y); var s2_1_2 = new Point( (s2_1[gapIndex].x + s2_1[gapIndex+1].x) / 2, s2_1[gapIndex+1].y); s2_1.splice(gapIndex + 1, 0, s2_1_1, s2_1_2); solutions.push(['s2', 's2_1', s2_1]); //Variant II var s2_2 = Point.cloneArray(s); var s2_2_1 = new Point( s2_2[gapIndex].x, (s2_2[gapIndex].y + s2_2[gapIndex+1].y)/2 ); var s2_2_2 = new Point( s2_2[gapIndex+1].x, (s2_2[gapIndex].y + s2_2[gapIndex+1].y)/2); s2_2.splice(gapIndex + 1, 0, s2_2_1, s2_2_2); solutions.push(['s2', 's2_2', s2_2]); //Variant III var s2_3 = Point.cloneArray(s); //find the amount (stored in delta) of pixels we need to move right so no intersection with a figure will be present //!See: /documents/specs/connected_figures_deltas.jpg file var eastExits = [s2_3[gapIndex].x + 20, s2_3[gapIndex+1].x + 20]; //add points X coordinates to be able to generate Variant III even in the absence of figures :p if(sBounds){ eastExits.push(sBounds[2] + 20); } if(eBounds){ eastExits.push(eBounds[2] + 20); } var eastExit = Util.max(eastExits); var s2_3_1 = new Point( eastExit, s2_3[gapIndex].y ); var s2_3_2 = new Point( eastExit, s2_3[gapIndex+1].y ); s2_3.splice(gapIndex + 1, 0, s2_3_1, s2_3_2); solutions.push(['s2', 's2_3', s2_3]); //Variant IV var s2_4 = Point.cloneArray(s); //find the amount (stored in delta) of pixels we need to move up so no intersection with a figure will be present //!See: /documents/specs/connected_figures_deltas.jpg file var northExits = [s2_4[gapIndex].y - 20, s2_4[gapIndex+1].y - 20]; //add points y coordinates to be able to generate Variant III even in the absence of figures :p if(sBounds){ northExits.push(sBounds[1] - 20); } if(eBounds){ northExits.push(eBounds[1] - 20); } var northExit = Util.min(northExits); var s2_4_1 = new Point( s2_4[gapIndex].x, northExit); var s2_4_2 = new Point( s2_4[gapIndex+1].x, northExit); s2_4.splice(gapIndex + 1, 0, s2_4_1, s2_4_2); solutions.push(['s2', 's2_4', s2_4]); //Variant V var s2_5 = Point.cloneArray(s); //find the amount (stored in delta) of pixels we need to move left so no intersection with a figure will be present //!See: /documents/specs/connected_figures_deltas.jpg file var westExits = [s2_5[gapIndex].x - 20, s2_5[gapIndex+1].x - 20]; //add points x coordinates to be able to generate Variant III even in the absence of figures :p if(sBounds){ westExits.push(sBounds[0] - 20); } if(eBounds){ westExits.push(eBounds[0] - 20); } var westExit = Util.min(westExits); var s2_5_1 = new Point( westExit, s2_5[gapIndex].y); var s2_5_2 = new Point( westExit, s2_5[gapIndex+1].y); s2_5.splice(gapIndex + 1, 0, s2_5_1, s2_5_2); solutions.push(['s2', 's2_5', s2_5]); //Variant VI var s2_6 = Point.cloneArray(s); //find the amount (stored in delta) of pixels we need to move down so no intersection with a figure will be present //!See: /documents/specs/connected_figures_deltas.jpg file var southExits = [s2_6[gapIndex].y + 20, s2_6[gapIndex+1].y + 20]; //add points y coordinates to be able to generate Variant III even in the absence of figures :p if(sBounds){ southExits.push(sBounds[3] + 20); } if(eBounds){ southExits.push(eBounds[3] + 20); } var southExit = Util.max(southExits); var s2_6_1 = new Point( s2_6[gapIndex].x, southExit); var s2_6_2 = new Point( s2_6[gapIndex+1].x, southExit); s2_6.splice(gapIndex + 1, 0, s2_6_1, s2_6_2); solutions.push(['s2', 's2_6', s2_6]); //FILTER solutions /*Algorithm * 0. solutions are ordered from minimmun nr of points to maximum >:) * 1. remove all solutions that are not orthogonal (mainly s0 solution) * 2. remove all solutions that go backward (we will not need them ever) * 3. remove all solutions with intersections * 4. pick first class of solutions with same nr of points (ex: 2) * 5. pick the first solution with 90 degree angles (less turnarounds) * (not interesteted) sort by length :p */ //1. filter non ortogonal solutions if(true){ Log.info("Filter orthogonal solutions. Initial number of solutions = " + solutions.length); var orthogonalSolution = []; for(var l=0; l:D*/ for(var s=0; s= 0 if is fine * The bigger the number the smooth the path is *@author Alex Gheorghiu **/ _scorePath:function(v, smooth){ var n = v.length; if(n < 3){ return -1; } var score = 0; for(var i=1;i **/ _validPath:function(v){ var n = v.length; if(n < 3){ return false; } for(var i=1;i */ connectionPointGetByXY:function(x,y, type){ var id = -1; for(var i=0; i */ connectionPointGetByXYRadius: function(x,y, radius, type, ignoreConPoint) { var curId = -1, closestId = -1, curX, curY, minDistance = -1, curDistance; for (curX = x - radius; curX <= x + radius; curX++) { for (curY = y - radius; curY <= y + radius; curY++) { if ( !ignoreConPoint.contains(curX,curY) ) { curId = this.connectionPointGetByXY(curX, curY, type); if (curId !== -1) { curDistance = Math.sqrt( Math.pow(curX - x, 2) + Math.pow(curY - y, 2) ); if (minDistance === -1 || curDistance < minDistance) { minDistance = curDistance; closestId = curId; } } } } } return closestId; }, /**Reset color to all connection points *@author Alex Gheorghiu **/ connectionPointsResetColor : function(){ for(var i=0; i */ connectionPointCreate:function(parentId, point, type){ var conPoint = new ConnectionPoint(parentId, point.clone(), this.connectionPointCurrentId++, type); this.connectionPoints.push(conPoint); return conPoint; }, /**Adds an existing ConnectionPoint to the connectionPoints array *@param {ConnectionPoint} connectionPoint - the ConnectionPoint to add * * @author Artyom Pokatilov **/ connectionPointAdd:function(connectionPoint){ this.connectionPoints.push(connectionPoint); }, /** Removes the connectionPoints associated with a parent (it can be either Figure or Connector) * @param {Number} parentId - the figure id * @author Alex Gheorghiu */ connectionPointRemoveAllByParent:function(parentId){ for(var i=0; i */ connectionPointGetAllByParent:function(parentId){ var collectedPoints = []; for(var connectionPoint in this.connectionPoints){ if(this.connectionPoints[connectionPoint].parentId == parentId){ collectedPoints.push(this.connectionPoints[connectionPoint]); } } return collectedPoints; }, /** Returns a subset of whole ConnectionPoints that belong to a figure or a connector *@param {Number} parentId - the figure or connector's id whom subset we want *@param {String} type - ConnectionPoint.TYPE_FIGURE or ConnectionPoint.TYPE_CONNECTOR. No more guessing. *@return {Array}{ConnectionPoint}s *@author Alex Gheorghiu */ connectionPointGetAllByParentIdAndType:function(parentId, type){ var collectedPoints = []; for(var cpIndex in this.connectionPoints){ if(this.connectionPoints[cpIndex].parentId == parentId && this.connectionPoints[cpIndex].type == type){ collectedPoints.push(this.connectionPoints[cpIndex]); } } return collectedPoints; }, /** Get the connectionPoint the mouse is over * @param {Number} x - coordinate * @param {Number} y - coordinate * @param {Number} parentFigureId - the subset to obtain (optional) * If the parentFigureId is null we will get extend our search to all the ConnectionPoints on the canvas * If the parentFigureId is positive we will limit our search to that shape (Figure or Connector) * If the parentFigureId is negative we will search on all Canvas but except that shape (Figure or Connector) * @return {ConnectionPoint} that fit the criteria or null if none present * @author Alex Gheorghiu */ connectionPointOver:function(x, y, parentFigureId){ var foundedConnectionPoint = null; if(typeof(parentFigureId) == 'number'){ //we have a Figure id specified if(parentFigureId < 0 ){ //search whole canvas except a figure for(var canvasConnectionPoint in this.connectionPoints){ if(this.connectionPoints[canvasConnectionPoint].parentId != -parentFigureId && this.connectionPoints[canvasConnectionPoint].contains(x,y)){ this.connectionPoints[canvasConnectionPoint].color = ConnectionPoint.OVER_COLOR; foundedConnectionPoint = this.connectionPoints[canvasConnectionPoint]; } } } else{ //search only a figure var figureConnectionPoints = this.connectionPointGetAllByParent(parentFigureId); for(var figureConnectionPoint in figureConnectionPoints){ if(figureConnectionPoints[figureConnectionPoint].contains(x,y)){ figureConnectionPoints[figureConnectionPoint].color = ConnectionPoint.OVER_COLOR; foundedConnectionPoint = figureConnectionPoints[figureConnectionPoint]; } } } } else{ //search whole canvas for(var connectionPoint in this.connectionPoints){ if(this.connectionPoints[connectionPoint].contains(x,y)){ this.connectionPoints[connectionPoint].color = ConnectionPoint.OVER_COLOR; foundedConnectionPoint = this.connectionPoints[connectionPoint]; } } } return foundedConnectionPoint; }, /**Paints ALL ConnectionPoints that are attached to a shape (Figure or Connector) *@param {Context} context - the HTML5 canvas' context *@param {Number} parentFigureId - the the parent figure's ID */ connectionPointPaint:function(context, parentFigureId){ var figureConnectionPoints = this.connectionPointGetAllByParent(parentFigureId); for(var conPoint in figureConnectionPoints){ figureConnectionPoints[conPoint].paint(context); } }, /** * Transform all ConnectionPoints of a Figure * @param {Number} fId - the the parent figure's ID * @param {Matrix} matrix - the transformation matrix **/ connectionPointTransform:function(fId, matrix){ var fCps = this.connectionPointGetAllByParent(fId); var currentFigure = STACK.figureGetById(fId); //Log.info("ConnectionManager: connectionPointTransform()....1"); //get all shape's connection points for(var i = 0 ; i < fCps.length; i++){ //transform figure's connection points (no go for the other side) fCps[i].transform(matrix); //Log.info("\tConnectionManager: connectionPointTransform()....2"); /* TODO: can we have more than one Glue for single ConnectionPoint? Do we need this cycle? */ //get all glues for current connection point var glues = this.glueGetByFirstConnectionPointId(fCps[i].id); var gluesLength = glues.length; //Log.info("\tConnectionManager: connectionPointTransform()" + fCps[i].id + " glues = " + gluesLength); for(var j = 0; j < gluesLength; j++){ // get the ConnectionPoint from other side of current Glue (from the Connector) var firstCP = this.connectionPointGetById(glues[j].id2); // get the Connector - parent of firstCP ConnectionPoint var connector = this.connectorGetById(firstCP.parentId); // get ConnectionPoints of the Connector var cCPs = this.connectionPointGetAllByParent(connector.id); /* In case of having connections where first or second ConnectionPoint glued automatically - * current ConnectionPoints can change it's position and maybe to another ConnectionPoints. * Variables used in finding solution of ConnectionPoints. we need to find * who is the start Figure, end Figure, starting Glue, ending Glue, etc*/ var startPoint = cCPs[0].point; var endPoint = cCPs[1].point; var startFigure; var endFigure; var startBounds; var endBounds; var automaticStart; var automaticEnd; // if current Figure is glued with startPoint of the Connector if (firstCP.id == cCPs[0].id) { // ConnectionPoint connected with moved (transformed) Figure must be moved (transformed) as well cCPs[0].transform(matrix); // in this case current Figure is startFigure of the connection startFigure = currentFigure; // get Glue for second ConnectionPoint of the Connector var endGlue = this.glueGetBySecondConnectionPointId(cCPs[1].id)[0]; // get Figure's ConnectionPoint which is glued with second ConnectionPoint of the Connector var endFigureCP = endGlue ? this.connectionPointGetById(endGlue.id1) : null; // get endFigure as parent of endFigureCP endFigure = endFigureCP ? STACK.figureGetById(endFigureCP.parentId) : null; // if startPoint has automatic Glue -> connection has automatic start automaticStart = glues[j].automatic; // if endPoint has Glue and it's automatic -> connection has automatic end automaticEnd = endGlue && endGlue.automatic; } else { // if current Figure is glued with endPoint of the Connector // ConnectionPoint connected with moved (transformed) Figure must be moved (transformed) as well cCPs[1].transform(matrix); // in this case current Figure is endFigure of the connection endFigure = currentFigure; // get Glue for first ConnectionPoint of the Connector var startGlue = this.glueGetBySecondConnectionPointId(cCPs[0].id)[0]; // get Figure's ConnectionPoint which is glued with first ConnectionPoint of the Connector var startFigureCP = startGlue ? this.connectionPointGetById(startGlue.id1) : null; // get startFigure as parent of startFigureCP startFigure = startFigureCP ? STACK.figureGetById(startFigureCP.parentId) : null; // if startPoint has Glue and it's automatic -> connection has automatic start automaticStart = startGlue && startGlue.automatic; // if endPoint has automatic Glue -> connection has automatic end automaticEnd = glues[j].automatic; } startBounds = startFigure ? startFigure.getBounds(): null; endBounds = endFigure ? endFigure.getBounds() : null; //find best candidate for start and end point var candidate = CONNECTOR_MANAGER.getClosestPointsOfConnection( automaticStart, //start automatic automaticEnd, //end automatic startFigure ? startFigure.id : -1, //start figure's id startPoint, //start point endFigure ? endFigure.id : -1, //end figure's id endPoint //end point ); //solutions DIAGRAMO.debugSolutions = CONNECTOR_MANAGER.connector2Points(connector.type, candidate[0], candidate[1], startBounds, endBounds); // apply solution to Connector connector.applySolution(DIAGRAMO.debugSolutions); // update position of Connector's ConnectionPoints cCPs[0].point = connector.turningPoints[0].clone(); cCPs[1].point = connector.turningPoints[connector.turningPoints.length - 1].clone(); //Log.info("\t\tConnectionManager: connectionPointTransform() - connector's point " + conCp); // firstCP.transform(matrix); //get attached connector // var con = this.connectorGetById(conCp.parentId); //adjust attached Connector through the ConnectionPoint // con.adjust(matrix, conCp.point.clone()); // this.connectorAdjustByConnectionPoint(glues[j].id2 /*, x, y*/); } } /* //get all shape's automatic glues var automaticGlues = this.glueGetByFigureId(fId); var automaticGluesLength = automaticGlues.length; //transform all {ConnectionPoint}s and {Connector}s in automatic connection for(j = 0; j < automaticGluesLength; j++){ //get the ConnectionPoint from other side of the glue (from the connector) var conCpId = automaticGlues[j].id2; var conCp = this.connectionPointGetById(conCpId); //Log.info("\t\tConnectionManager: connectionPointTransform() - connector's point " + conCp); conCp.transform(matrix); //adjust attached Connector through the ConnectionPoint this.connectorAdjustByConnectionPoint(conCpId); } */ //Log.info("ConnectionManager: connectionPointTransform()..."); }, /** * See if two {ConnectionPoint}s are connected *@param id1 - a {ConnectionPoint}'s id *@param id2 - another {ConnectionPoint}'s id *@author Alex Gheorghiu **/ connectionPointIsConnected:function(id1,id2){ for (var i=0; i < this.glues.length; i++){ if( (id1 == this.glues[i].id1 && id2 == this.glues[i].id2) || (id1 == this.glues[i].id2 && id2 == this.glues[i].id1) ){ return true; } } return false; }, /** *See if a {ConnectionPoint} has {Glue}s attached to it *@param {Number} conectionPointId - the {ConnectionPoint}'s id *@return true - if we have {Glue}s with it or false if not *@author Zack Newsham *@author Alex Gheorghiu **/ connectionPointHasGlues:function(conectionPointId){ for (var i=0; i */ glueGetByFirstConnectionPointId:function(pointId){ var collectedGlues = []; var currentGlue; for(var i=0; i *TODO: as second id is usually connector AND a Connector can not be connected to more than one Figure *then it should be ONLY one returning value */ glueGetBySecondConnectionPointId:function(pointId){ var collectedGlues = []; for(var i=0; i **/ glueCreate:function(firstId, secondId, automatic){ var glue = new Glue(firstId, secondId, automatic); this.glues.push(glue); return glue; }, /**Adds an existing Glue to the glues array *@param {Glue} glue - the Glue to add * * @author Artyom Pokatilov **/ glueAdd:function(glue){ this.glues.push(glue); }, /**Removes all the {Glue}s based on it's two IDs *@param {Number} id1 - the id of the first shape (usually the Figure) *@param {Number} id2 - the id of the second shape (usually the Connector) * *@author Alex Gheorghiu **/ glueRemoveByIds:function(id1, id2){ for (var i=0; i **/ glueRemoveAllByFirstId:function(id){ for (var i=0; i **/ glueRemoveAllBySecondId:function(id){ for (var i=0; i */ glueGetAllByIds:function(firstId, secondId){ var collectedGlues = []; for(var i=0; i *@author Alex **/ connectionCloudPaint: function(context) { if (DIAGRAMO.visualMagnet) { if (currentCloud.length) { //draw only if we have a cloud var conPoint1 = this.connectionPointGetById(currentCloud[0]); var conPoint2 = this.connectionPointGetById(currentCloud[1]); var centerX = (conPoint2.point.x + conPoint1.point.x) / 2; //x coordinates of the ellipse var centerY = (conPoint2.point.y + conPoint1.point.y) / 2; //y coordinates of the ellipse var radiusX = ConnectorManager.CLOUD_RADIUS; var radiusY = ConnectorManager.CLOUD_RADIUS / 2; /* * Using formula from http://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Application:_finding_the_angle_of_a_right_triangle * 2. Finding angle from arctan, where opposite is (conPoint2.point.y - conPoint1.point.y) * and adjacent is (conPoint2.point.x - conPoint1.point.x) */ var rotationAngle = Math.atan( (conPoint2.point.y - conPoint1.point.y) / (conPoint2.point.x - conPoint1.point.x)); context.save(); context.beginPath(); if (context.ellipse) { // if context.ellipse() function implemented context.ellipse(centerX, centerY, radiusX, radiusY, rotationAngle, 0, 2 * Math.PI, false); } else { // TODO: when ellipse will be implemented in all browsers - remove it /*We will construct an ellipse by 2 Bezier curves * Algorithm described in /web/editor/test/issues/3/Demo.html * and on a Bitbucket (if it still alive :)) https://bitbucket.org/scriptoid/diagramo/issue/3/highlight-about-to-connect-connection#comment-8643442 * */ var width_two_thirds = radiusX * 4 / 3; var dx1 = Math.sin(rotationAngle) * radiusY; var dy1 = Math.cos(rotationAngle) * radiusY; var dx2 = Math.cos(rotationAngle) * width_two_thirds; var dy2 = Math.sin(rotationAngle) * width_two_thirds; var P3x = centerX - dx1; var P3y = centerY + dy1; var P2x = P3x + dx2; var P2y = P3y + dy2; var P4x = P3x - dx2; var P4y = P3y - dy2; var P6x = centerX + dx1; var P6y = centerY - dy1; var P1x = P6x + dx2; var P1y = P6y + dy2; var P5x = P6x - dx2; var P5y = P6y - dy2; context.moveTo(P6x, P6y); context.bezierCurveTo(P1x, P1y, P2x, P2y, P3x, P3y); context.bezierCurveTo(P4x, P4y, P5x, P5y, P6x, P6y); } context.lineWidth = ConnectorManager.CLOUD_LINEWIDTH; context.strokeStyle = ConnectorManager.CLOUD_STROKE_STYLE; context.stroke(); context.closePath(); context.restore(); } } } }