You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1830 lines
74 KiB
Plaintext
1830 lines
74 KiB
Plaintext
/**
|
|
* 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 <alex@scriptoid.com>
|
|
**/
|
|
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<newConnectionManager.connectors.length;i++){
|
|
localLog += '\n' + newConnectionManager.connectors[i].toString();
|
|
}
|
|
|
|
//2 - load connection points
|
|
localLog += '\nCONNECTION POINTS';
|
|
newConnectionManager.connectionPoints = ConnectionPoint.loadArray(o.connectionPoints);
|
|
for(var i=0;i<newConnectionManager.connectionPoints.length; i++){
|
|
localLog += "\n" + newConnectionManager.connectionPoints[i].toString();
|
|
}
|
|
//alert(str);
|
|
|
|
// newConnectionManager.connectionPointSelectedIndex = o.connectionPointSelectedIndex;
|
|
newConnectionManager.connectionPointCurrentId = o.connectionPointCurrentId;
|
|
|
|
|
|
//3 - load glues
|
|
localLog += '\nGLUES';
|
|
newConnectionManager.glues = Glue.loadArray(o.glues);
|
|
|
|
//localLog += 'Connection manager has ' + newConnectionManager.glues.length + " glues";
|
|
for(var i=0;i<newConnectionManager.glues.length; i++){
|
|
localLog += "\n" + newConnectionManager.glues[i].toString();
|
|
}
|
|
|
|
//alert(localLog);
|
|
|
|
newConnectionManager.connectionMode = o.connectionMode;
|
|
|
|
return newConnectionManager ;
|
|
}
|
|
|
|
ConnectorManager.prototype = {
|
|
|
|
constructor : ConnectorManager,
|
|
|
|
/**
|
|
*Performs a deeps equals
|
|
*@param {ConnectorManager} anotherConnectionManager - the other object to compare against
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
equals : function(anotherConnectionManager){
|
|
if(!anotherConnectionManager instanceof ConnectorManager){
|
|
return false;
|
|
}
|
|
|
|
//test Connectors
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
if(!this.connectors[i].equals(anotherConnectionManager.connectors[i])){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//test ConnectionPoints
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
if(!this.connectionPoints[i].equals(anotherConnectionManager.connectionPoints[i])){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//test Glues
|
|
for(var i=0; i<this.glues.length; i++){
|
|
if(!this.glues[i].equals(anotherConnectionManager.glues[i])){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return this.connectionPointCurrentId == anotherConnectionManager.connectionPointCurrentId
|
|
&& this.connectionMode == anotherConnectionManager.connectionMode;
|
|
},
|
|
|
|
|
|
/**Export the entire ConnectionManager to SVG format*/
|
|
toSVG : function(){
|
|
var r = '';
|
|
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
r += this.connectors[i].toSVG();
|
|
}
|
|
|
|
return r;
|
|
},
|
|
|
|
/****************************************************************/
|
|
/**************************Connector*****************************/
|
|
/****************************************************************/
|
|
|
|
/**
|
|
* Creates a new connector. Also store the connector into the pool of connectors.
|
|
*
|
|
* @param {Point} startPoint
|
|
* @param {Point} endPoint
|
|
* @param {String} type of Connector. It can be either 'straight' or 'jagged'
|
|
* @return {Number} the id of the newly created Connector
|
|
*/
|
|
connectorCreate:function(startPoint,endPoint,type){
|
|
//get a new id for Connector
|
|
var id = STACK.generateId();
|
|
|
|
//create and save connector
|
|
this.connectors.push(new Connector(startPoint,endPoint,type, id));
|
|
|
|
//create ConnectionPoints for Connector
|
|
this.connectionPointCreate(id, startPoint, ConnectionPoint.TYPE_CONNECTOR);
|
|
this.connectionPointCreate(id, endPoint, ConnectionPoint.TYPE_CONNECTOR);
|
|
|
|
//log
|
|
var c = this.connectorGetById(id);
|
|
//alert('log:connectorCreate: connector has ' + c.turningPoints.length);
|
|
|
|
return id;
|
|
},
|
|
|
|
|
|
/**
|
|
* Remove a connector and all other objects( ConnectionPoints, Glues) linked to it
|
|
* @param {Connector} connector - the connector you want to remove
|
|
*/
|
|
connectorRemove:function(connector){
|
|
this.connectorRemoveById(connector.id, true);
|
|
},
|
|
|
|
|
|
/**
|
|
*Remove a connector by Id
|
|
*@param {Number} connectorId - the {Connector}'s id
|
|
*@param {Boolean} cascade - if cascade is true it will delete the (linked) ConnectionPoints and Glues
|
|
*/
|
|
connectorRemoveById:function(connectorId, cascade){
|
|
|
|
if(cascade){
|
|
|
|
//remove all affected Glues
|
|
var cCPs = this.connectionPointGetAllByParent(connectorId); //get all connection points
|
|
for(var k=0; k<cCPs.length; k++){
|
|
this.glueRemoveAllBySecondId(cCPs[k].id);
|
|
}
|
|
|
|
//remove all affected ConnectionPoints
|
|
this.connectionPointRemoveAllByParent(connectorId);
|
|
}
|
|
|
|
//remove the connector
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
if(this.connectors[i].id == connectorId){
|
|
this.connectors.splice(i,1);
|
|
break;
|
|
}//end if
|
|
}//end for
|
|
},
|
|
|
|
|
|
/**Paints all connectors and highlight the selected one
|
|
*@param {Context} context - a reference to HTML5's canvas
|
|
*@param {Number} highlightedConnectorId - the id of the highlighted connector
|
|
*@author Zack
|
|
*@author Alex
|
|
*TODO: maybe all painting should not be made in managers
|
|
**/
|
|
connectorPaint:function(context, highlightedConnectorId){
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
//PAINT A CONNECTOR
|
|
|
|
//1 - paint highlight color
|
|
//detect if we have a currenly selected Connector and "highlight" its ConnectionPoints
|
|
if(this.connectors[i].id == highlightedConnectorId){
|
|
//paint a lime line underneath the connector
|
|
context.save();
|
|
if(this.connectors[i].style.lineWidth == null){
|
|
this.connectors[i].style.lineWidth = 1;
|
|
}
|
|
var oldStyle = this.connectors[i].style.clone();
|
|
var oldWidth = this.connectors[i].style.lineWidth;
|
|
this.connectors[i].style = new Style();
|
|
this.connectors[i].style.lineWidth = parseInt(oldWidth) + 2;
|
|
this.connectors[i].style.strokeStyle = "lime";
|
|
this.connectors[i].paint(context);
|
|
this.connectors[i].style = oldStyle;
|
|
context.restore();
|
|
}
|
|
|
|
//2 - paint the connector
|
|
this.connectors[i].paint(context);
|
|
|
|
//3 - paint the connection points (as they are on top of the connector)
|
|
if(this.connectors[i].id == highlightedConnectorId){
|
|
this.connectionPointPaint(context, this.connectors[i].id)
|
|
}
|
|
|
|
// //(if selected) activate the handlers for the connector
|
|
// if(i == this.connectorSelectedIndex){
|
|
// HandleManager.shapeSet(this.connectors[i]);
|
|
// HandleManager.paint(context);
|
|
// }
|
|
|
|
}//end for
|
|
},
|
|
|
|
/**
|
|
* Disconnects all Connectors (actually one) that have a ConnectionPoint
|
|
* @param {Number} conectionPointId - connectionPoint to disconnect
|
|
* should only ever be called using the conPoint of a Connector, but could work either way
|
|
*/
|
|
connectorDisconnect:function(conectionPointId){
|
|
if(this.connectionPointHasGlues(conectionPointId)){
|
|
this.glueRemove(conectionPointId);
|
|
}
|
|
},
|
|
|
|
|
|
/** Simplifies the connection of 2 ConnectionPoints
|
|
* @param {ConnectionPoint} connectionPoint1 - connectionPoint on a figure/connector
|
|
* @param {ConnectionPoint} connectionPoint2 - connectionPoint on a figure/connector
|
|
*
|
|
* TODO: Is it mandatory that one ConnectionPoint is from a figure an the another one from a Connector?
|
|
*/
|
|
connectorConnect:function(connectionPoint1, connectionPoint2){
|
|
//connect the two figures, they were highlighted, so now unhighlight them
|
|
connectionPoint1.color = ConnectionPoint.CONNECTED_COLOR;
|
|
connectionPoint2.color = ConnectionPoint.CONNECTED_COLOR;
|
|
|
|
//select the figure we jusfigurest connected to
|
|
var figurePoint;
|
|
var nonFigurePoint;
|
|
|
|
//find out which one is from a connector
|
|
//set the connectors point to the figures point, has a "snap to" effect
|
|
if(connectionPoint1.type == ConnectionPoint.TYPE_FIGURE){
|
|
figurePoint = connectionPoint1;
|
|
nonFigurePoint = connectionPoint2;
|
|
}
|
|
else{
|
|
figurePoint = connectionPoint2;
|
|
nonFigurePoint = connectionPoint1;
|
|
}
|
|
|
|
//make connector's point be identical with Figure's point
|
|
nonFigurePoint.point.x = figurePoint.point.x;
|
|
nonFigurePoint.point.y = figurePoint.point.y;
|
|
|
|
//are these connectionPoints already connected, if not connect them now?
|
|
if(!this.connectionPointIsConnected(nonFigurePoint.id,figurePoint.id)){
|
|
this.glueCreate(nonFigurePoint.id,figurePoint.id, false);
|
|
}
|
|
|
|
//if we are now connected at two places, make the line jagged.
|
|
var connector = this.connectorGetById(nonFigurePoint.parentId);
|
|
|
|
if(connector != null){
|
|
if(this.connectionPointHasGlues(this.connectionPointGetAllByParent(connector.id)[0].id) //is Connector's first ConnectionPoint glued
|
|
&& this.connectionPointHasGlues(this.connectionPointGetAllByParent(connector.id)[1].id) //is Connector's second ConnectionPoint glued
|
|
&& connector.type == Connector.TYPE_JAGGED) //is Connector's type jagged
|
|
{
|
|
//TODO: it seems that Connector does not have a parameter....!?
|
|
alert('Problem connector has ' + connector.turningPoints.length + " points");
|
|
//connector.jagged();
|
|
connector.jaggedReloaded();
|
|
connector.redraw();
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/** Selects a connector using x and y coordinates, same as figure and handle
|
|
* @param {Number} x - the x coordinate
|
|
* @param {Number} y - the y coordinate
|
|
*/
|
|
connectorSelectXY:function(x,y){
|
|
//try to pick the new selected connector
|
|
this.connectorSelectedIndex = -1;
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
if(this.connectors[i].contains(x,y)){
|
|
this.connectorSelectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
/** Returns the currently selected connector
|
|
* or null if none available
|
|
*/
|
|
connectorGetSelected:function(){
|
|
if(this.connectorSelectedIndex!=-1){
|
|
return this.connectors[this.connectorSelectedIndex];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
|
|
/**Get a Connector by its id
|
|
*@param {Number} connectorId
|
|
*@return {Connetor} if founded or null if none finded
|
|
**/
|
|
connectorGetById:function(connectorId){
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
if(this.connectors[i].id == connectorId){
|
|
return this.connectors[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
|
|
/**Returns the id of the connector the mouse is over.
|
|
*It actually return the first connector we found in a vicinity of that point
|
|
*@param {Number} x - the x coord
|
|
*@param {Number} y - the y coord
|
|
*@return {Number} - the id of the connector or -1 if no connector found
|
|
*
|
|
*TODO: Note: We are picking the Connector the most far from user as if
|
|
*we iterate from 0 to connectors.lenght we are going from back to front, similar
|
|
*to painting
|
|
*/
|
|
connectorGetByXY:function(x,y){
|
|
var id = -1;
|
|
for(var i=0; i<this.connectors.length; i++){
|
|
if(this.connectors[i].near(x, y, 3)){
|
|
id = this.connectors[i].id;
|
|
break;
|
|
}
|
|
}
|
|
return id;
|
|
},
|
|
|
|
|
|
/**
|
|
*Returns the id value of connector for the given coordinates of it's middle text
|
|
*@param {Number} x - the value on Ox axis
|
|
*@param {Number} y - the value on Oy axis
|
|
*@return {Number} - id value of connector (parent of the text primitive with XY) otherwise -1
|
|
*@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
|
**/
|
|
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 <artyom.pokatilov@gmail.com>
|
|
**/
|
|
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 <alex@scriptoid.com>
|
|
**/
|
|
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<solutions.length; l++){
|
|
var solution = solutions[l][2];
|
|
if(Util.orthogonalPath(solution)){
|
|
orthogonalSolution.push(solutions[l]);
|
|
}
|
|
}
|
|
solutions = orthogonalSolution;
|
|
Log.info("\n\tOrthogonalSolutions = " + solutions.length);
|
|
}
|
|
|
|
//2. filter backward solutions
|
|
if(true){
|
|
//do not allow start and end points to coincide - ignore them
|
|
if(startPoint.equals(endPoint)){
|
|
Log.info("Start and end point coincide...skip backward solution. I think we will just fall on s0 :)");
|
|
}
|
|
else{
|
|
Log.info("Filter backward solutions. Initial number of solutions = " + solutions.length);
|
|
var forwardSolutions = [];
|
|
var temp = '';
|
|
for(var l=0; l<solutions.length; l++){
|
|
var solution = solutions[l][2];
|
|
if(Util.forwardPath(solution)){
|
|
forwardSolutions.push(solutions[l]);
|
|
}
|
|
else{
|
|
temp = temp + "\n\t" + solution;
|
|
}
|
|
}
|
|
solutions = forwardSolutions;
|
|
Log.info("\n\t ForwardSolutions = " + solutions.length);
|
|
if(solutions.length == 0){
|
|
Log.info("Discarded solutions: " + temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//3. Filter non intersecting solutions
|
|
if(true){
|
|
Log.info("Filter non intersecting solutions. Initial number of solutions = " + solutions.length);
|
|
var nonIntersectionSolutions = []
|
|
for(var l=0; l<solutions.length; l++){
|
|
var solution = solutions[l][2];
|
|
//Log.info("Solution id= " + solutions[l][1] + ' nr points = ' + solution.length + ", points = " + solution);
|
|
var intersect = false;
|
|
|
|
var innerLines = solution.slice(); //just a shallow copy
|
|
|
|
/*If any bounds just trim the solution. So we avoid the strange case when a connection
|
|
*startes from a point on a figure and ends inside of the same figure, but not on a connection point*/
|
|
if(eBounds || sBounds){
|
|
//i0nnerLines = innerLines.slice(0, innerLines.length - 1);
|
|
innerLines = innerLines.slice(1, innerLines.length - 1);
|
|
//Log.info("\t eBounds present,innerLines nr. points = " + innerLines.length + ", points = " + innerLines);
|
|
}
|
|
|
|
|
|
|
|
//now test for intersection
|
|
if(sBounds){
|
|
intersect = intersect || Util.polylineIntersectsRectangle(innerLines, sBounds);
|
|
}
|
|
if(eBounds){
|
|
intersect = intersect || Util.polylineIntersectsRectangle(innerLines, eBounds);
|
|
}
|
|
|
|
if(!intersect){
|
|
nonIntersectionSolutions.push(solutions[l]);
|
|
}
|
|
}
|
|
|
|
//If all solutions intersect than this is destiny :) and just ignore the intersection filter
|
|
if(nonIntersectionSolutions.length != 0){
|
|
//reasign to solutions
|
|
solutions = nonIntersectionSolutions;
|
|
}
|
|
|
|
Log.info("\n\t nonIntersectionSolutions = " + solutions.length);
|
|
}
|
|
|
|
|
|
//4. get first class of solutions with same nr of points
|
|
if(true){
|
|
Log.info("Get first class of solutions with same nr of points");
|
|
if(solutions.length == 0){
|
|
alert("This is not possible");
|
|
}
|
|
|
|
var firstSolution = solutions[0][2]; //pick first solution
|
|
var nrOfPoints = firstSolution.length;
|
|
var sameNrPointsSolution = [];
|
|
|
|
for(var l=0; l<solutions.length; l++){
|
|
var solution = solutions[l][2];
|
|
if(solution.length == nrOfPoints){
|
|
sameNrPointsSolution.push(solutions[l]);
|
|
}
|
|
}
|
|
|
|
solutions = sameNrPointsSolution;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*5. Pick the first solution with 90 degree angles (less turnarounds)
|
|
*in case we have more than one solution in our class
|
|
*/
|
|
if(true){
|
|
Log.info("pick the first solution with 90 degree angles (less turnarounds)");
|
|
var solIndex = 0;
|
|
for(var l=0; l<solutions.length; l++){
|
|
var solution = solutions[l][2];
|
|
if(Util.scorePath( solutions[solIndex][2] ) < Util.scorePath( solutions[l][2] ) ){
|
|
solIndex = l;
|
|
}
|
|
}
|
|
solutions = [solutions[solIndex]];
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
//SMOOTHING curve
|
|
if(type === Connector.TYPE_ORGANIC){
|
|
this.smoothOrganic(solutions);
|
|
}
|
|
//END SMOOTHING curve
|
|
|
|
Log.groupEnd();
|
|
|
|
Log.level = oldLogLevel;
|
|
|
|
return solutions;
|
|
},
|
|
|
|
|
|
/**
|
|
* Tries to smooth the solution.
|
|
* Made mainly for organic connectors
|
|
* @param {Array} solutions - 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)
|
|
* */
|
|
smoothOrganic: function(solutions){
|
|
var option = 3;
|
|
|
|
switch(option){
|
|
case 0:
|
|
//do nothing
|
|
break;
|
|
|
|
case 1: //add intermediate points
|
|
//Add the middle point for start and end segment so that we "force" the
|
|
//curve to both come "perpendicular" on bounds and also make the curve
|
|
//"flee" more from bounds (on exit)
|
|
for(var s=0; s<solutions.length; s++){
|
|
var solTurningPoints = solutions[s][2];
|
|
|
|
//first segment
|
|
var a1 = solTurningPoints[0];
|
|
var a2 = solTurningPoints[1];
|
|
var startMiddlePoint = Util.getMiddle(a1, a2);
|
|
solTurningPoints.splice(1,0, startMiddlePoint);
|
|
|
|
//last segment
|
|
var a3 = solTurningPoints[solTurningPoints.length - 2];
|
|
var a4 = solTurningPoints[solTurningPoints.length - 1];
|
|
var endMiddlePoint = Util.getMiddle(a3, a4);
|
|
solTurningPoints.splice(solTurningPoints.length - 1, 0, endMiddlePoint);
|
|
}
|
|
break;
|
|
|
|
case 2: //remove points
|
|
for(var s=0; s<solutions.length; s++){
|
|
var solType= solutions[s][0];
|
|
if(solType == 's1' || solType == 's2'){
|
|
var solTurningPoints = solutions[s][2];
|
|
solTurningPoints.splice(1,1);
|
|
solTurningPoints.splice(solTurningPoints.length - 2, 1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
/*remove colinear point for s1 as it seems that more colinear points do not look good
|
|
* on organic solutions >:D*/
|
|
for(var s=0; s<solutions.length; s++){
|
|
var solType= solutions[s][0];
|
|
if(solType == 's1'){
|
|
var solTurningPoints = solutions[s][2];
|
|
var reducedSolution = Util.collinearReduction(solTurningPoints);
|
|
solutions[s][2] = reducedSolution;
|
|
}
|
|
}
|
|
break;
|
|
}//end switch
|
|
|
|
},
|
|
|
|
|
|
/**Score a ortogonal path made out of Points
|
|
*Iterates over a set of points (minimum 3)
|
|
*For each 3 points if the 3rd one is after the 2nd
|
|
* on the same line we add +1 if the 3rd is up or down rlated to the 2nd we do not do anything
|
|
* and if the 3rd goes back we imediatelly retun -1
|
|
*@param {Array} v - an array of {Point}s
|
|
*@param {Boolean} smooth - if true the smoothest path will have a greater score, if false the most jagged
|
|
* will have a bigger score.
|
|
*@return {Number} - -1 if the path is wrong (goes back) or something >= 0 if is fine
|
|
* The bigger the number the smooth the path is
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
_scorePath:function(v, smooth){
|
|
var n = v.length;
|
|
if(n < 3){
|
|
return -1;
|
|
}
|
|
|
|
var score = 0;
|
|
for(var i=1;i<n-1;i++){
|
|
if(v[i-1].x == v[i].x && v[i].x == v[i+1].x){ //on the same vertical
|
|
if(signum(v[i+1].y - v[i].y) == signum(v[i].y - v[i-1].y)){ //same direction
|
|
if(smooth){
|
|
score++;
|
|
}
|
|
}
|
|
else{ //going back - no good
|
|
return -1;
|
|
}
|
|
}
|
|
else if(v[i-1].y == v[i].y && v[i].y == v[i+1].y){ //on the same horizontal
|
|
if(signum(v[i+1].x - v[i].x) == signum(v[i].x - v[i-1].x)){ //same direction
|
|
if(smooth){
|
|
score++;
|
|
}
|
|
}
|
|
else{ //going back - no good
|
|
return -1;
|
|
}
|
|
}
|
|
else{ //not on same vertical nor horizontal
|
|
if(!smooth){
|
|
score++;
|
|
}
|
|
//do nothing; is like adding 0
|
|
}
|
|
}
|
|
|
|
return score;
|
|
},
|
|
|
|
|
|
/**
|
|
*Tests if a vector of points is a valid path (not going back)
|
|
*@param {Array} v - an {Array} of {Point}s
|
|
*@return {Boolean} - true if path is valid, false otherwise
|
|
*@author Alex <alex@scriptoid.com>
|
|
**/
|
|
_validPath:function(v){
|
|
var n = v.length;
|
|
if(n < 3){
|
|
return false;
|
|
}
|
|
|
|
for(var i=1;i<n-1;i++){
|
|
if(v[i-1].x == v[i].x && v[i].x == v[i+1].x){ //on the same vertical
|
|
if(signum(v[i+1].y - v[i].y) != signum(v[i].y - v[i-1].y)){ //going back
|
|
return false;
|
|
}
|
|
}
|
|
else if(v[i-1].y == v[i].y && v[i].y == v[i+1].y){ //on the same horizontal
|
|
if(signum(v[i+1].x - v[i].x) != signum(v[i].x - v[i-1].x)){ //going back
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
|
|
/**Reset this ConnectionManager*/
|
|
reset:function(){
|
|
this.connectors = [];
|
|
this.connectorSelectedIndex = -1;
|
|
this.connectionPoints = [];
|
|
this.connectionPointSelectedIndex = -1;
|
|
this.connectionPointCurrentId = 0;
|
|
},
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
/**************************ConnectionPoint*****************************/
|
|
/****************************************************************/
|
|
|
|
/**Returns the selected connection point
|
|
*@return a {ConnectionPoint} that is stored at position selectedConnectionPointIndex
|
|
*or null if selectedConnectionPointIndex is not set (equal to -1)
|
|
**/
|
|
connectionPointGetSelected:function(){
|
|
if(this.connectionPointSelectedIndex == -1){
|
|
return null
|
|
}
|
|
return this.connectionPoints[this.connectionPointSelectedIndex];
|
|
},
|
|
|
|
/** Returns a connection point id based on an x and y and the ConnectionPoint.RADIUS
|
|
* It will pick the first one that matches the criteria
|
|
*@param {Number} x - the x coordinates of the point
|
|
*@param {Number} y - the y coordinates of the point
|
|
*@param {String} type - the type of connector to select. Can be 'connector'(ConnectionPoint.TYPE_CONNECTOR)
|
|
* or 'figure' (ConnectionPoint.TYPE_FIGURE)
|
|
*@return {Number} the Id of the {ConnectionPoint} or -1 if none found
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
*/
|
|
connectionPointGetByXY:function(x,y, type){
|
|
var id = -1;
|
|
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
if( this.connectionPoints[i].contains(x,y) && this.connectionPoints[i].type == type ){
|
|
id = this.connectionPoints[i].id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
/** Returns closest connection point id based on an x, y, radius and the ConnectionPoint.RADIUS
|
|
* It will pick the first one that matches the criteria
|
|
*@param {Number} x - the x coordinates of the point
|
|
*@param {Number} y - the y coordinates of the point
|
|
*@param {Number} radius - max distance from (x,y) point
|
|
*@param {String} type - the type of connector to select. Can be 'connector'(ConnectionPoint.TYPE_CONNECTOR)
|
|
* or 'figure' (ConnectionPoint.TYPE_FIGURE)
|
|
*@param {ConnectionPoint} ignoreConPoint - the ConnectionPoint to ignore in search
|
|
*@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
|
*/
|
|
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 <alex@scriptoid.com>
|
|
**/
|
|
connectionPointsResetColor : function(){
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
this.connectionPoints[i].color = ConnectionPoint.NORMAL_COLOR;
|
|
}
|
|
},
|
|
|
|
/** Creates a new ConnectionPoint. Beside creating the ConnectionPoint it will also
|
|
* inject the id and store the ConnectionPoint
|
|
*
|
|
* @param {Number} parentId - the id of the parent ( Figure or Connector )this ConnectionPoint will belong to
|
|
* @param {Point} point - the location
|
|
* @param {String} type - the type of parent. It can be 'figure' (ConnectionPoint.TYPE_FIGURE)
|
|
* or 'connector' {ConnectionPoint.TYPE_CONNECTOR}
|
|
* @return {ConnectionPoint} with a proper id set
|
|
*
|
|
* @author Alex Gheorghiu <alex@scriptoid.com>
|
|
*/
|
|
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 <artyom.pokatilov@gmail.com>
|
|
**/
|
|
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 <alex@scriptoid.com>
|
|
*/
|
|
connectionPointRemoveAllByParent:function(parentId){
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
if(this.connectionPoints[i].parentId == parentId){
|
|
this.connectionPoints.splice(i,1);
|
|
i--;
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
/** Returns a ConnectionPoint based on its id
|
|
* or null if none finded
|
|
*@param {Number} connectionPointId - the id
|
|
*@return {ConnectionPoint}
|
|
*/
|
|
connectionPointGetById:function(connectionPointId){
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
if(this.connectionPoints[i].id == connectionPointId){
|
|
return this.connectionPoints[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
|
|
/** Returns a ConnectionPoint based on its id
|
|
* or null if none finded
|
|
*@param {Number} parentId - the id of the parent
|
|
*@param {Number} x - the x of the point
|
|
*@param {Number} y - the y of the point
|
|
*@return {ConnectionPoint}
|
|
*/
|
|
connectionPointGetByParentAndCoordinates:function(parentId, x, y){
|
|
for(var i=0; i<this.connectionPoints.length; i++){
|
|
if(this.connectionPoints[i].parentId == parentId
|
|
&& this.connectionPoints[i].point.x == x
|
|
&& this.connectionPoints[i].point.y == y
|
|
|
|
)
|
|
{
|
|
return this.connectionPoints[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
|
|
/** 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
|
|
*@return {Array}{ConnectionPoint}s
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
*/
|
|
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 <alex@scriptoid.com>
|
|
*/
|
|
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 <alex@scriptoid.com>
|
|
*/
|
|
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 <alex@scriptoid.com>
|
|
**/
|
|
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 <zack_newsham@yahoo.co.uk>
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
connectionPointHasGlues:function(conectionPointId){
|
|
for (var i=0; i<this.glues.length; i++){
|
|
if(conectionPointId == this.glues[i].id1 || conectionPointId == this.glues[i].id2){
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/****************************************************************/
|
|
/**************************Glue*****************************/
|
|
/****************************************************************/
|
|
|
|
/** Returns all {Glue}s that have the first Id equals with a certain id value
|
|
*@param {Number} pointId - {Figure}'s {ConnectionPoint} id
|
|
*@return {Array}{Glue}s
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
*/
|
|
glueGetByFirstConnectionPointId:function(pointId){
|
|
var collectedGlues = [];
|
|
var currentGlue;
|
|
for(var i=0; i<this.glues.length; i++){
|
|
currentGlue = this.glues[i];
|
|
if(currentGlue.id1 == pointId){
|
|
collectedGlues.push(currentGlue);
|
|
}
|
|
}
|
|
return collectedGlues;
|
|
},
|
|
|
|
/** Returns all {Glue}s that have the second Id equals with a certain id value
|
|
*@param {Number} pointId - {Connector}'s {ConnectionPoint} id
|
|
*@return {Array}{Glue}s
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
*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<this.glues.length; i++){
|
|
if(this.glues[i].id2 == pointId){
|
|
collectedGlues.push(this.glues[i]);
|
|
}
|
|
}
|
|
return collectedGlues;
|
|
},
|
|
|
|
|
|
/**Creates a new {Glue} and store it into the glue database. Use this instead
|
|
*of creating the Glues by simply "new" operator
|
|
*
|
|
*@param {Number} firstId - the id of the first {ConnectionPoint} - usually the {Figure}'s {ConnectionPoint} id
|
|
*@param {Number} secondId - the id of the second {ConnectionPoint} - usually the {Connector}'s {ConnectionPoint} id
|
|
*@return {Glue} - the newly created Glue
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
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 <artyom.pokatilov@gmail.com>
|
|
**/
|
|
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 <alex@scriptoid.com>
|
|
**/
|
|
glueRemoveByIds:function(id1, id2){
|
|
for (var i=0; i<this.glues.length; i++){
|
|
if(id1 == this.glues[i].id1 && id2 == this.glues[i].id2){
|
|
this.glues.splice(i,1);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
/**Removes all the {Glue}s based on first Id (usually the Figure)
|
|
*@param {Number} id - the id of the first shape (usually the Figure)
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
glueRemoveAllByFirstId:function(id){
|
|
for (var i=0; i<this.glues.length; i++){
|
|
if(id == this.glues[i].id1){
|
|
this.glues.splice(i,1);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
/**Removes all the {Glue}s based on second Id (usually the Connector)
|
|
*@param {Number} id - the id of the second shape (usually the Connector)
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
**/
|
|
glueRemoveAllBySecondId:function(id){
|
|
for (var i=0; i<this.glues.length; i++){
|
|
if(id == this.glues[i].id2){
|
|
this.glues.splice(i,1);
|
|
}
|
|
}
|
|
},
|
|
|
|
/** Returns all {Glue}s that have the first Id equals with a certain id value
|
|
*@param {Number} firstId - first id (usually {Figure}'s id)
|
|
*@param {Number} secondId - second id (usually {Connector}'s id)
|
|
*@return {Array}{Glue}s
|
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
|
*/
|
|
glueGetAllByIds:function(firstId, secondId){
|
|
var collectedGlues = [];
|
|
for(var i=0; i<this.glues.length; i++){
|
|
if(this.glues[i].id1 == firstId && this.glues[i].id2 == secondId){
|
|
collectedGlues.push(this.glues[i]);
|
|
}
|
|
}
|
|
return collectedGlues;
|
|
},
|
|
|
|
|
|
/**
|
|
*Paints the Cloud into a Context
|
|
*@param {Context} context - the 2D context
|
|
*@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
|
*@author Alex <alex@scriptoid.com>
|
|
**/
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|