openct-tasks/_common/modules/pemFioi/visual-graph-1.1.js

1226 lines
49 KiB
JavaScript

function VisualGraph(id, paper, graph, graphDrawer, autoDraw, vertexVisualInfo, edgeVisualInfo) {
this.id = id;
this.paper = paper;
this.graph = graph;
this.graphDrawer = graphDrawer;
this.hasDrawing = false;
if(vertexVisualInfo) {
this.vertexVisualInfo = vertexVisualInfo;
}
else {
this.vertexVisualInfo = {};
}
if(edgeVisualInfo) {
this.edgeVisualInfo = edgeVisualInfo;
}
else {
this.edgeVisualInfo = {};
}
this.edgeRaphaels = {};
this.vertexRaphaels = {};
var self = this;
this.listener = {
addVertex: function(id, info) {
self._drawVertex(id, info);
return true;
},
addEdge: function(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo) {
self._drawEdge(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo);
return true;
},
removeVertex: function(id, info) {
self._eraseVertex(id, info);
delete self.vertexVisualInfo[id];
return true;
},
removeEdge: function(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo) {
self._eraseEdge(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo);
delete self.edgeVisualInfo[id];
return true;
}
};
this.priority = 1000;
this._attachRaphaelID = function(elements, id) {
for(var iElement in elements) {
elements[iElement].data("id", id);
}
};
this._initDrawer = function() {
if(!this.hasDrawing && this.graphDrawer.init) {
this.graphDrawer.init(this.paper, this.graph, this);
}
this.hasDrawing = true;
};
this.setAutoDraw = function(autoDraw) {
if(autoDraw == this.autoDraw) {
return;
}
this.autoDraw = autoDraw;
if(autoDraw) {
this.graph.addPostListener(this.id, this.listener, this.priority);
}
else {
this.graph.removePostListener(this.id);
}
};
this.redraw = function() {
this._removeGraphics();
var vertices = this.graph.getAllVertices();
for(var iVertex in vertices) {
this._drawVertex(vertices[iVertex], this.graph.getVertexInfo(vertices[iVertex]));
}
var edges = this.graph.getAllEdges();
for(var iEdge in edges) {
var edgeVertices = this.graph.getEdgeVertices(edges[iEdge]);
var vertex1 = edgeVertices[0];
var vertex2 = edgeVertices[1];
var vertex1Info = this.graph.getVertexInfo(vertex1);
var vertex2Info = this.graph.getVertexInfo(vertex2);
var edgeInfo = this.graph.getEdgeInfo(edges[iEdge]);
this._drawEdge(edges[iEdge], vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo);
}
if(this.graphDrawer.drawingComplete) {
this.graphDrawer.drawingComplete();
}
};
this._removeGraphics = function() {
if(!this.hasDrawing) {
return;
}
for(var edgeID in this.edgeRaphaels) {
this._eraseEdge(edgeID);
}
for(var vertexID in this.vertexRaphaels) {
this._eraseVertex(vertexID);
}
if(this.graphDrawer.deinit) {
this.graphDrawer.deinit();
}
this.hasDrawing = false;
};
this._drawVertex = function(id, info) {
self._initDrawer();
if(!self.vertexVisualInfo[id]) {
self.vertexVisualInfo[id] = {};
}
if(self.graphDrawer.drawVertex) {
self.vertexRaphaels[id] = self.graphDrawer.drawVertex(id, info, self.vertexVisualInfo[id]);
self._attachRaphaelID(self.vertexRaphaels[id], id);
}
else {
self.vertexRaphaels[id] = [];
}
};
this._drawEdge = function(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo) {
self._initDrawer();
if(!self.edgeVisualInfo[id]) {
self.edgeVisualInfo[id] = {};
}
if(self.graphDrawer.drawEdge) {
self.edgeRaphaels[id] = self.graphDrawer.drawEdge(id, vertex1, vertex2, vertex1Info, vertex2Info, self.vertexVisualInfo[vertex1], self.vertexVisualInfo[vertex2], edgeInfo, self.edgeVisualInfo[id]);
self._attachRaphaelID(self.edgeRaphaels[id], id);
}
else {
self.edgeRaphaels[id] = [];
}
};
this._eraseVertex = function(id, info) {
if(this.graphDrawer.eraseVertex) {
this.graphDrawer.eraseVertex(id, info, self.vertexVisualInfo[id]);
}
for(var iElement in this.vertexRaphaels[id]) {
var element = this.vertexRaphaels[id][iElement];
if(element && element.remove) {
element.remove();
}
}
delete self.vertexRaphaels[id];
};
this._eraseEdge = function(id, vertex1, vertex2, vertex1Info, vertex2Info, edgeInfo) {
if(this.graphDrawer.eraseEdge) {
this.graphDrawer.eraseEdge(id, vertex1, vertex2, vertex1Info, vertex2Info, self.vertexVisualInfo[vertex1], self.vertexVisualInfo[vertex2], edgeInfo, self.edgeVisualInfo[id]);
}
for(var iElement in this.edgeRaphaels[id]) {
var element = this.edgeRaphaels[id][iElement];
if(element && element.remove) {
element.remove();
}
}
delete self.edgeRaphaels[id];
};
this.remove = function() {
this._removeGraphics();
this.setAutoDraw(false);
};
this.pushVertexRaphael = function(id, element) {
if(!this.vertexRaphaels[id]) {
return;
}
this.vertexRaphaels[id].push(element);
element.data("id", id);
};
this.popVertexRaphael = function(id) {
if(!this.vertexRaphaels[id] || !this.vertexRaphaels[id].length) {
return;
}
return this.vertexRaphaels[id].pop();
};
this.pushEdgeRaphael = function(id, element) {
if(!this.edgeRaphaels[id]) {
return;
}
this.edgeRaphaels[id].push(element);
element.data("id", id);
};
this.popEdgeRaphael = function(id) {
if(!this.edgeRaphaels[id] || !this.edgeRaphaels[id].length) {
return;
}
return this.edgeRaphaels[id].pop();
};
this.setVertexVisualInfo = function(id, info) {
this.vertexVisualInfo[id] = info;
};
this.getVertexVisualInfo = function(id) {
return this.vertexVisualInfo[id];
};
this.setEdgeVisualInfo = function(id, info) {
this.edgeVisualInfo[id] = info;
};
this.getEdgeVisualInfo = function(id) {
return this.edgeVisualInfo[id];
};
this.getRaphaelsFromID = function(id) {
if(this.vertexRaphaels[id]) {
return this.vertexRaphaels[id];
}
if(this.edgeRaphaels[id]) {
return this.edgeRaphaels[id];
}
return [];
};
this.elementToFront = function(id) {
var raphaels = this.getRaphaelsFromID(id);
for(var iElement in raphaels) {
raphaels[iElement].toFront();
}
};
this.setPaper = function(paper) {
this.paper = paper;
};
this.getPaper = function() {
return this.paper;
};
this.setDrawer = function(graphDrawer) {
this.graphDrawer = graphDrawer;
};
this.getGraph = function() {
return this.graph;
};
this.toJSON = function() {
return JSON.stringify({
vertexVisualInfo: this.vertexVisualInfo,
edgeVisualInfo: this.edgeVisualInfo,
minGraph: this.graph.toMinimized()
});
};
this.setAutoDraw(autoDraw);
if(autoDraw) {
this.redraw();
}
}
VisualGraph.fromJSON = function(visualGraphStr, id, paper, graph, graphDrawer, autoDraw) {
var visualInfo = JSON.parse(visualGraphStr);
if(!graph) {
graph = Graph.fromMinimized(visualInfo.minGraph);
}
return new VisualGraph(id, paper, graph, graphDrawer, autoDraw, visualInfo.vertexVisualInfo, visualInfo.edgeVisualInfo);
};
function SimpleGraphDrawer(circleAttr, lineAttr, vertexDrawer, autoMove, vertexMover, thickMode, innerLineAttr) {
this.circleAttr = circleAttr;
this.rectAttr = circleAttr;
this.boxLineAttr = circleAttr;
this.minBoxW = 2*circleAttr.r + 50;
this.lineAttr = lineAttr;
this.edgeLabelAttr;
this.vertexLabelAttr = {
"font-size": 15,
"font-family": "sans-serif"
};
this.vertexContentAttr = {
"font-size": 15,
"font-family": "sans-serif"
};
this.edgeLabelAttr = {
"font-size": 15,
"font-family": "sans-serif"
};
this.edgeClickAreaAttr = {
"stroke-width": 20,
"stroke": "red",
"opacity": 0
};
this.init = function(paper, graph, visualGraph) {
this.paper = paper;
this.graph = graph;
this.visualGraph = visualGraph;
this.customElements = {};
this.originalPositions = {};
};
this.setDrawVertex = function(fct) {
this.drawVertex = fct;
};
this.setDrawEdge = function(fct) {
this.drawEdge = fct;
};
this.setEdgeLabelAttr = function(attr) {
this.edgeLabelAttr = attr;
};
this.setCircleAttr = function(circleAttr) {
this.circleAttr = circleAttr;
};
this.setRectAttr = function(rectAttr) {
this.rectAttr = rectAttr;
};
this.setBoxLineAttr = function(boxLineAttr) {
this.boxLineAttr = boxLineAttr;
};
this.setMinBoxW = function(w) {
this.minBoxW = w;
};
this.setLineAttr = function(lineAttr) {
this.lineAttr = lineAttr;
};
this.setVertexLabelAttr = function(attr) {
this.vertexLabelAttr = attr;
};
this.setVertexContentAttr = function(attr) {
this.vertexContentAttr = attr;
};
this.setEdgeClickAreaAttr = function(attr) {
this.edgeClickAreaAttr = attr;
};
this.drawVertex = function(id, info, visualInfo) {
var pos = this._getVertexPosition(visualInfo);
this.originalPositions[id] = pos;
var label = (info.label) ? info.label : "";
if(!visualInfo.tableMode){
var node = this.paper.circle(pos.x, pos.y).attr(this.circleAttr);
var labelRaph = this.paper.text(pos.x,pos.y,label).attr(this.vertexLabelAttr);
if(info.terminal && !info.initial){
var terminalCircle = this.paper.circle(pos.x, pos.y).attr("r",this.circleAttr.r + 5);
var result = [node,labelRaph,terminalCircle];
this._addCustomElements(id, [labelRaph,terminalCircle]);
}else if(info.initial && !info.terminal){
var initialArrow = this.paper.path("M" + (pos.x - 3*this.circleAttr.r) + "," + pos.y + "H" + (pos.x - this.circleAttr.r)).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]+1);
var result = [node,labelRaph,initialArrow];
this._addCustomElements(id, [labelRaph,initialArrow]);
}else if(info.initial && info.terminal){
var terminalCircle = this.paper.circle(pos.x, pos.y).attr("r",this.circleAttr.r + 5);
var initialArrow = this.paper.path("M" + (pos.x - 3*this.circleAttr.r) + "," + pos.y + "H" + (pos.x - this.circleAttr.r)).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]+1);
var result = [node,labelRaph,initialArrow,terminalCircle];
this._addCustomElements(id, [labelRaph,initialArrow,terminalCircle]);
}else{
var result = [node,labelRaph];
this._addCustomElements(id, [labelRaph]);
}
}else{
/* table mode */
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,visualInfo.wCorr);
var w = boxSize.w;
var h = boxSize.h;
var x = pos.x - w/2;
var y = pos.y - h/2;
var labelHeight = 2*this.vertexLabelAttr["font-size"];
var node = this.paper.rect(x,y,w,h).attr(this.rectAttr);
var labelRaph = this.paper.text(pos.x, y + labelHeight/2, label).attr(this.vertexLabelAttr);
var line = this.paper.path("M"+x+","+(y + labelHeight)+"H"+(x + w)).attr(this.boxLineAttr);
var textAlign = this.vertexContentAttr["text-anchor"] || "middle";
switch(textAlign){
case "middle":
var contentX = pos.x;
break;
case "start":
var contentX = pos.x - boxSize.w/2 + 10;
break;
case "end":
var contentX = pos.x + boxSize.w/2 - 10;
break;
}
var content = this.paper.text(contentX, y + labelHeight + (h - labelHeight)/2,content).attr(this.vertexContentAttr);
if(info.initial && !info.terminal){
var initialArrow = this.paper.path("M" + (x - 2*this.circleAttr.r) + "," + pos.y + "H" + x).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]+1);
var result = [node,labelRaph,line,content,initialArrow];
}else if(!info.initial && info.terminal){
var terminalFrame = this.paper.rect(x - 5, y - 5, w + 10, h + 10, this.circleAttr.r + 5);
var result = [node,labelRaph,line,content,terminalFrame];
}else if(info.initial && info.terminal){
var terminalFrame = this.paper.rect(x - 5, y - 5, w + 10, h + 10, this.circleAttr.r + 5);
var initialArrow = this.paper.path("M" + (x - 2*this.circleAttr.r) + "," + pos.y + "H" + x).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]+1);
var result = [node,labelRaph,line,content,initialArrow,terminalFrame];
}else{
var result = [node,labelRaph,line,content];
}
this._addCustomElements(id, result);
}
if(vertexDrawer) {
var raphaels = vertexDrawer(id, info, pos.x, pos.y);
this._addCustomElements(id, raphaels);
result = result.concat(raphaels);
}
return result;
};
this.getBoxSize = function(content,wCorrection) {
var wCorr = wCorrection || 0;
var margin = 10;
var labelHeight = 2*this.vertexLabelAttr["font-size"];
// var textSize = this.getTextSize(content);
var tempText = this.paper.text(0,0,content).attr(this.vertexContentAttr);
var textBBox = tempText.getBBox();
tempText.remove();
var minW = this.minBoxW;
var minH = labelHeight + (2*this.vertexLabelAttr["font-size"]);
// var w = Math.max(0.7*textSize.nbCol * this.vertexContentAttr["font-size"], minW);
var w = Math.max(textBBox.width + 2*margin,minW);
// var h = Math.max(labelHeight + (1 + textSize.nbLines) * this.vertexContentAttr["font-size"] + 2*margin, minH);
var h = Math.max(textBBox.height + labelHeight + 2*margin, minH);
return { w: w + wCorr, h: h };
};
this.updateVertex = function(id) {
var info = this.graph.getVertexInfo(id);
var visualInfo = this.visualGraph.getVertexVisualInfo(id);
this.visualGraph._eraseVertex(id);
this.drawVertex(id,info,visualInfo);
};
this.drawEdge = function(id, vertex1, vertex2) {
if(thickMode) {
var path = this._getThickEdgePath(vertex1, vertex2);
return [this.paper.path(path).attr(this.lineAttr).toBack(), this.paper.path(path).attr(innerLineAttr)];
}
else {
var clickArea = this.paper.path(this._getEdgePath(id)).attr(this.edgeClickAreaAttr).toBack();
var path = this.paper.path(this._getEdgePath(id)).attr(this.lineAttr).toBack();
var info = this.graph.getEdgeInfo(id);
var labelText = info.label || "";
var labelPos = this.getLabelPos(id, vertex1, vertex2);
var label = this.paper.text(labelPos.x,labelPos.y,labelText).attr(this.edgeLabelAttr);
return [path,label,clickArea];
}
};
this._getVertexPosition = function(visualInfo) {
if(typeof visualInfo == "undefined"){ // IE8
return { x: 0, y: 0}
}
if(visualInfo.x === undefined || visualInfo.x === null) {
visualInfo.x = 0;
visualInfo.y = 0;
}
return {
x: visualInfo.x,
y: visualInfo.y
};
};
this.getVertexPosition = function(id) {
return this._getVertexPosition(this.visualGraph.getVertexVisualInfo(id));
};
this._addCustomElements = function(id, raphaels) {
// Save original attributes. This allows us to move the object later by transformation.
this.customElements[id] = [];
for(var iElement in raphaels) {
var raphael = raphaels[iElement];
this.customElements[id].push({
raphael: raphael,
originalAttrs: $.extend(true, {}, raphael.attrs)
});
}
};
this.moveVertex = function(id, x, y) {
var info = this.visualGraph.getVertexVisualInfo(id);
info.x = x;
info.y = y;
var raphaels = this.visualGraph.getRaphaelsFromID(id);
raphaels[0].attr({
cx: x,
cy: y
});
// Move the custom Raphael objects.
if(vertexMover) {
vertexMover(id, raphaels, x, y);
}
if(autoMove) {
this._moveCustomElements(id, x, y);
}
var childrenIDs = this.graph.getChildren(id);
for(var iChild in childrenIDs) {
this.refreshEdgePosition(id, childrenIDs[iChild]);
}
if(this.graph.directed) {
var parentIDs = this.graph.getParents(id);
for(var iParent in parentIDs) {
this.refreshEdgePosition(parentIDs[iParent], id);
}
}
};
this._moveCustomElements = function(id, x, y) {
var elements = this.customElements[id];
var transformation = ["T", x - this.originalPositions[id].x, y - this.originalPositions[id].y];
for(var iElement in elements) {
var element = elements[iElement];
// Paths get transformed using Raphael.transformPath,
// for compatibility. Other objects get transformed normally.
if(element.raphael.type === "path") {
element.raphael.attr({path: Raphael.transformPath(element.originalAttrs.path, transformation)});
}
else {
element.raphael.transform(transformation);
}
}
};
this.refreshEdgePosition = function(vertex1, vertex2) {
var edges = this.graph.getEdgesFrom(vertex1, vertex2);
var info1 = this.visualGraph.getVertexVisualInfo(vertex1);
var info2 = this.visualGraph.getVertexVisualInfo(vertex2);
var newPath;
for(var iEdge in edges) {
var edgeID = edges[iEdge];
var raphaels = this.visualGraph.getRaphaelsFromID(edgeID);
if(thickMode) {
newPath = this._getThickEdgePath(vertex1, vertex2);
}
else {
newPath = this._getEdgePath(edgeID);
}
raphaels[0].attr("path", newPath); // edge
raphaels[2].attr("path", newPath); // click area
var info = this.graph.getEdgeInfo(edgeID);
var labelText = info.label || "";
var labelPos = this.getLabelPos(edgeID, vertex1, vertex2);
raphaels[1].attr({x:labelPos.x,y:labelPos.y,text:labelText});
if(thickMode) {
raphaels[1].attr("path", newPath);
}
}
};
this._getEdgePath = function(edgeID) {
var edgeVisualInfo = this.visualGraph.getEdgeVisualInfo(edgeID);
var vertices = this.graph.getEdgeVertices(edgeID);
var vertex1 = vertices[0];
var vertex2 = vertices[1];
if(edgeVisualInfo["radius-ratio"] || vertex1 === vertex2){
return this._getCurvedEdgePath(vertex1,vertex2,edgeID);
}
var vInfo1 = this.visualGraph.getVertexVisualInfo(vertex1);
var vInfo2 = this.visualGraph.getVertexVisualInfo(vertex2);
var x1 = vInfo1.x, y1 = vInfo1.y, x2 = vInfo2.x, y2 = vInfo2.y;
var r = this.circleAttr.r;
if(!vInfo2.tableMode){
/*
* We want to draw an edge from the center of one circle toward the center
* of another, but only up to its surface. Otherwise the arrow would be
* inside the target circle.
* The line between centers goes from x1,y1 to x2,y2, and we want to
* chop length r from it. We call the denote by w,h the displacement
* from x2,y2.
*/
// Same X coordinate.
if(x1 == x2) {
if(y1 < y2) {
return ["M", x1, y1, "L", x2, y2 - r];
}
else {
return ["M", x1, y1, "L", x2, y2 + r];
}
}
// Swap for convenience. x1,y1 is always to the left.
var swap = false;
if(x1 > x2) {
swap = true;
var temp = x1;
x1 = x2;
x2 = temp;
temp = y1;
y1 = y2;
y2 = temp;
}
// We have h^2 + w^2 = r^2 and (y2-y1)/(x2-x1) = h/w.
var slope = 1.0 * (y2 - y1) / (x2 - x1);
var w = (r / Math.sqrt((1 + slope * slope)));
var h = (slope * w);
if(!swap) {
return ["M", x1, y1, "L", x2 - w, y2 - h];
}
else {
return ["M", x2, y2, "L", x1 + w, y1 + h];
}
}else{
var info = this.graph.getVertexInfo(vertex2);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vInfo2.wCorr);
var alpha = this.getAngleBetween(x1,y1,x2,y2);
var pos2 = this.getSurfacePointFromAngle(x2,y2,boxSize.w,boxSize.h,alpha);
return ["M", x1, y1, "L", pos2.x, pos2.y];
}
};
this._getThickEdgePath = function(vertex1, vertex2) {
var info1 = this.visualGraph.getVertexVisualInfo(vertex1);
var info2 = this.visualGraph.getVertexVisualInfo(vertex2);
var x1 = info1.x, y1 = info1.y, x2 = info2.x, y2 = info2.y;
return ["M", x1, y1, "L", x2, y2];
};
this._getCurvedEdgePath = function(vertex1,vertex2,edgeID) {
var vInfo1 = this.visualGraph.getVertexVisualInfo(vertex1);
var vInfo2 = this.visualGraph.getVertexVisualInfo(vertex2);
var edgeVisualInfo = this.visualGraph.getEdgeVisualInfo(edgeID);
var x1 = vInfo1.x, y1 = vInfo1.y, x2 = vInfo2.x, y2 = vInfo2.y;
var r = this.circleAttr.r;
var D = Math.sqrt(Math.pow((x2-x1),2) + Math.pow((y2-y1),2)); // distance between vertex1 and vertex2
var R = D*edgeVisualInfo["radius-ratio"]; // arc radius, between D/2 and +inf (almost straight line at D*50).
var s = edgeVisualInfo.sweep || 0; // sweep flag
var l = edgeVisualInfo["large-arc"] || 0; // large arc flag
/* Calculation of the coordinates of the target point at the surface of the target vertex */
var angle = (l) ? (Math.asin(D/(2*R)) + Math.PI) : Math.asin(D/(2*R));
if(!vInfo2.tableMode){
if(y1 !== y2 && x1 !== x2){
var A = (x2 - x1)/(y1 - y2);
var B = (x1*x1 + y1*y1 + r*r - x2*x2 - y2*y2 - Math.pow((r*Math.sin(angle)),2) - Math.pow((D - r*Math.cos(angle)),2))/(2*(y1 - y2));
var a = 1 + A*A;
var b = 2*(A*B - A*y2 - x2);
var c = x2*x2 + y2*y2 + B*B - 2*y2*B - r*r;
var delta = b*b - 4*a*c;
if(y1 > y2){
var x3 = (s) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}else{
var x3 = (s) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}
var y3 = A*x3 + B;
}else if(vertex1 === vertex2){
angle = edgeVisualInfo.angle || 0;
R = (edgeVisualInfo["radius-ratio"]) ? edgeVisualInfo["radius-ratio"]*r : 1.5*r;
x1 = x2 + r*Math.sin(Math.PI*angle/180);
y1 = y2 + r*Math.cos(Math.PI*angle/180);
var x3 = x2 - r*Math.sin(Math.PI*angle/180);
var y3 = y2 - r*Math.cos(Math.PI*angle/180);
l = 1;
edgeVisualInfo.angle = angle;
edgeVisualInfo["radius-ratio"] = R/r;
}else if(y1 === y2){
var x3 = (x1*x1 + r*r - x2*x2 - Math.pow((r*Math.sin(angle)),2) - Math.pow((D - r*Math.cos(angle)),2))/(2*(x1-x2));
var a = 1;
var b = -2*y2;
var c = Math.pow((x3 - x2),2) + y2*y2 - r*r;
var delta = b*b - 4*a*c;
if(x1 < x2){
var y3 = (s) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}else{
var y3 = (s) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a)
}
}else if(x1 === x2){
var y3 = (y1*y1 + r*r - y2*y2 - Math.pow((r*Math.sin(angle)),2) - Math.pow((D - r*Math.cos(angle)),2))/(2*(y1-y2));
var a = 1;
var b = -2*x2;
var c = Math.pow((y3 - y2),2) + x2*x2 - r*r;
var delta = b*b - 4*a*c;
if(y1 > y2){
var x3 = (s) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}else{
var x3 = (s) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}
}
return [ "M", x1, y1, "A", R, R, 0, l, s, x3, y3 ];
}else{
/* table mode */
var info = this.graph.getVertexInfo(vertex2);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vInfo2.wCorr);
if(vertex1 === vertex2){
/* loop */
angleCenter = edgeVisualInfo.angle || 0; // angle between center of vertex and projection of center of loop on the surface (in deg with trigonometric orientation)
var angleCorr = (angleCenter*Math.PI/180)%(2*Math.PI); // to match angle orientation of other functions
angleCorr = this.bindAngle(angleCorr);
R = (edgeVisualInfo["radius-ratio"]) ? edgeVisualInfo["radius-ratio"]*r : 1.5*r;
var beta = Math.atan(boxSize.h/boxSize.w); // angle between center of vertex and corner of box
/* loop center stays at R/2 from box surface */
if(angleCorr <= beta && angleCorr > -beta){
/* right side */
var alpha1 = Math.PI - Math.atan(R*Math.sqrt(3)/(boxSize.w) + Math.tan(angleCorr)); // angle between center of vertex and arrow point
var alpha2 = Math.PI - Math.atan(-R*Math.sqrt(3)/(boxSize.w) + Math.tan(angleCorr)); // angle between center of vertex and arrow start
}else if(angleCorr <= Math.PI + beta && angleCorr > Math.PI - beta){
/* left side */
var alpha1 = -Math.atan(R*Math.sqrt(3)/(boxSize.w) + Math.tan(angleCorr));
var alpha2 = -Math.atan(-R*Math.sqrt(3)/(boxSize.w) + Math.tan(angleCorr));
}else if(angleCorr > beta && angleCorr <= Math.PI - beta){
/* top */
var alpha1 = Math.PI/2 + Math.atan(-R*Math.sqrt(3)/(boxSize.h) + 1/Math.tan(angleCorr));
var alpha2 = Math.PI/2 + Math.atan(R*Math.sqrt(3)/(boxSize.h) + 1/Math.tan(angleCorr));
}else if(angleCorr > Math.PI + beta || angleCorr <= - beta){
/* bottom */
var alpha1 = 3*Math.PI/2 + Math.atan(-R*Math.sqrt(3)/(boxSize.h) + 1/Math.tan(angleCorr));
var alpha2 = 3*Math.PI/2 + Math.atan(R*Math.sqrt(3)/(boxSize.h) + 1/Math.tan(angleCorr));
}
var pos1 = this.getSurfacePointFromAngle(x1,y1,boxSize.w,boxSize.h,alpha1);
var pos2 = this.getSurfacePointFromAngle(x1,y1,boxSize.w,boxSize.h,alpha2);
l = 1;
edgeVisualInfo.angle = angleCenter;
edgeVisualInfo["radius-ratio"] = R/r;
return [ "M", pos2.x, pos2.y, "A", R, R, 0, l, s, pos1.x, pos1.y ];
}
var alpha = this.getAngleBetween(x1,y1,x2,y2);
if(vInfo1.tableMode){
if(s){
var alpha1 = (l) ? -alpha - angle : angle - alpha;
}else{
var alpha1 = (l) ? angle - alpha : -alpha - angle;
}
var info1 = this.graph.getVertexInfo(vertex1);
var content1 = (info1.content) ? info1.content : "";
var boxSize1 = this.getBoxSize(content1,vInfo1.wCorr);
var delta = Math.PI - alpha1;
var pos1 = this.getSurfacePointFromAngle(x1,y1,boxSize1.w,boxSize1.h,delta);
}else{
var pos1 = { x: x1, y: y1 };
}
if(s){
alpha = (l) ? alpha - angle : alpha + angle;
}else{
alpha = (l) ? alpha + angle : alpha - angle;
}
var pos2 = this.getSurfacePointFromAngle(x2,y2,boxSize.w,boxSize.h,alpha);
var D2 = Math.sqrt(Math.pow((pos2.x-pos1.x),2) + Math.pow((pos2.y-pos1.y),2));
var R2 = D2*edgeVisualInfo["radius-ratio"];
return [ "M", pos1.x, pos1.y, "A", R2, R2, 0, l, s, pos2.x, pos2.y ];
}
};
this.getAngleBetween = function(x1,y1,x2,y2) {
/* return angle between 2 points (between -PI/2 and PI/2) */
if(x2 != x1){
var alpha = Math.atan((y2 - y1)/(x2 - x1));
}else{
var alpha = (y2 > y1) ? Math.PI/2 : -Math.PI/2;
}
if(x1 > x2){
alpha += Math.PI;
}
return alpha;
};
this.getSurfacePointFromAngle = function(x,y,w,h,angle) {
/* return the coordinates of the point at the surface of a box at a given angle
* x,y : coordinates of the box center
* w,h: width and height of the box
* angle: angle from the center (in rad)
*/
var x2,y2;
var beta = Math.atan(h/w);
angle = this.bindAngle(angle);
if(angle <= beta && angle >= -beta){
// console.log("1");
x2 = x - w/2;
y2 = y - (w/2)*Math.tan(angle);
}else if(angle > beta && angle < Math.PI - beta){
// console.log("2");
x2 = x - (h/2)*Math.tan(Math.PI/2 - angle);
y2 = y - h/2;
}else if(angle <= (Math.PI + beta) && angle >= (Math.PI - beta)){
// console.log("3");
x2 = x + w/2;
y2 = y + (w/2)*Math.tan(angle);
}else if(angle > (Math.PI + beta) || angle < -beta){
// console.log("4");
x2 = x + (h/2)*Math.tan(Math.PI/2 - angle);
y2 = y + h/2;
}
return { x: x2, y: y2 };
};
this.reapplyAttr = function() {
var vertices = this.graph.getAllVertices();
for(var iVertex in vertices) {
this.visualGraph.getRaphaelsFromID(vertices[iVertex])[0].attr(this.circleAttr);
}
var edges = this.graph.getAllEdges();
for(var iEdge in edges) {
var raphaels = this.visualGraph.getRaphaelsFromID(edges[iEdge]);
raphaels[0].attr(this.lineAttr);
if(thickMode) {
raphaels[1].attr(innerLineAttr);
}
}
};
this.getDistanceFromVertex = function(id, xPos, yPos) {
var vertexPos = this.getVertexPosition(id);
var tableMode = this.visualGraph.getVertexVisualInfo(id).tableMode;
// var xDistance = xPos - vertexPos.x;
// var yDistance = yPos - vertexPos.y;
// var distanceFromCenter = distanceSquared(vertexPos.x,vertexPos.y,xPos,yPos);
var distanceFromCenter = Beav.Geometry.distance(vertexPos.x,vertexPos.y,xPos,yPos);
if(!tableMode){
if(distanceFromCenter <= this.circleAttr.r) {
return 0;
}
return distanceFromCenter - this.circleAttr.r;
}else{
/* table mode */
var angleWithCenter = this.getAngleBetween(vertexPos.x,vertexPos.y,xPos,yPos);
var info = this.graph.getVertexInfo(id);
var vInfo = this.visualGraph.getVertexVisualInfo(id);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vInfo.wCorr);
var surfacePoint = this.getSurfacePointFromAngle(vertexPos.x,vertexPos.y,boxSize.w,boxSize.h,angleWithCenter);
var surfaceFromCenter = Beav.Geometry.distance(vertexPos.x,vertexPos.y,surfacePoint.x,surfacePoint.y);
if(distanceFromCenter <= surfaceFromCenter) {
return 0;
}
return distanceFromCenter - surfaceFromCenter;
}
};
this.getDistanceFromEdge = function(id, xPos, yPos) {
var vInfo = this.visualGraph.getEdgeVisualInfo(id);
if(vInfo["radius-ratio"]){ // if curved edge
var edgeRaph = this.visualGraph.getRaphaelsFromID(id)[0];
var d = Infinity;
for(var length = 1; length < edgeRaph.getTotalLength(); length += 2){
var currPos = edgeRaph.getPointAtLength(length);
var currDist = Beav.Geometry.distance(currPos.x,currPos.y,xPos,yPos);
if(currDist < d){
d = currDist;
}
}
return d;
// var vertices = this.graph.getEdgeVertices(id);
// var vertex1Pos = this.visualGraph.getVertexVisualInfo(vertices[0]);
// var vertex2Pos = this.visualGraph.getVertexVisualInfo(vertices[1]);
// var x1 = vertex1Pos.x;
// var y1 = vertex1Pos.y;
// var x2 = vertex2Pos.x;
// var y2 = vertex2Pos.y;
// if(!vertex2Pos.tableMode){
// if(vertices[0] === vertices[1]){ // if loop
// var R = vInfo["radius-ratio"]*this.circleAttr.r;
// var angle = vInfo["angle"] || 0;
// var xc = x1 + R*Math.cos(angle*Math.PI/180);
// var yc = y1 - R*Math.sin(angle*Math.PI/180);
// }else{
// var D = Math.sqrt(Math.pow((x2-x1),2) + Math.pow((y2-y1),2));
// var R = vInfo["radius-ratio"]*D;
// var s = vInfo.sweep || 0;
// var l = vInfo["large-arc"] || 0;
// var cPos = this.getCenterPosition(R,s,l,vertex1Pos,vertex2Pos);
// var xc = cPos.x;
// var yc = cPos.y;
// }
// // var distFromCenter = Math.sqrt(Math.pow((xPos - xc),2) + Math.pow((yPos - yc),2));
// // return Math.abs(distFromCenter - R);
// }else{
// /* table mode */
// var info = this.graph.getVertexInfo(vertices[0]);
// var content = (info.content) ? info.content : "";
// var boxSize = this.getBoxSize(content,vertex1Pos.wCorr);
// if(vertices[0] === vertices[1]){
// /* loop */
// angleCenter = vInfo.angle || 0; // angle between center of vertex and projection of center of loop on the surface (in deg with trigonometric orientation)
// var angleCorr = (angleCenter*Math.PI/180)%(2*Math.PI);
// angleCorr = this.bindAngle(angleCorr);
// R = (vInfo["radius-ratio"]) ? vInfo["radius-ratio"]*this.circleAttr.r : 1.5*this.circleAttr.r;
// var beta = Math.atan(boxSize.h/boxSize.w); // angle between center of vertex and corner of box
// var surfPos = this.getSurfacePointFromAngle(x1,y1,boxSize.w,boxSize.h,Math.PI - angleCorr);
// /* loop center stays at R/2 from box surface */
// if(angleCorr <= beta && angleCorr > -beta){
// /* right side */
// var xc = surfPos.x + R/2;
// var yc = surfPos.y;
// }else if(angleCorr <= Math.PI + beta && angleCorr > Math.PI - beta){
// /* left side */
// var xc = surfPos.x - R/2;
// var yc = surfPos.y;
// }else if(angleCorr > beta && angleCorr <= Math.PI - beta){
// /* top */
// var xc = surfPos.x;
// var yc = surfPos.y - R/2;
// }else if(angleCorr > Math.PI + beta || angleCorr <= - beta){
// /* bottom */
// var xc = surfPos.x;
// var yc = surfPos.y + R/2;
// }
// }else{
// var param = this.getEdgeParam(id);
// var cPos = this.getCenterPosition(param.R,param.s,param.l,param.pos1,param.pos2);
// var xc = cPos.x;
// var yc = cPos.y;
// var R = param.R;
// }
// }
// var distFromCenter = Math.sqrt(distanceSquared(xc,yc,xPos,yPos));
// return Math.abs(distFromCenter - R);
}else{
var edgePath = this.visualGraph.getRaphaelsFromID(id)[0].attrs.path;
var x1, y1, x2, y2;
// In modern browsers the path is an array and we can get the endpoints
// directly. In old browsers it may be a comma separated string.
if($.isArray(edgePath)) {
if($.isArray(edgePath[0])) {
// Path a 2D array: [["M", x1, y1], ["L", x2, y2]]
x1 = parseInt(edgePath[0][1]);
y1 = parseInt(edgePath[0][2]);
x2 = parseInt(edgePath[1][1]);
y2 = parseInt(edgePath[1][2]);
}
else {
// Path is an array: ["M", x1, y1, "L", x2, y2]
x1 = parseInt(edgePath[1]);
y1 = parseInt(edgePath[2]);
x2 = parseInt(edgePath[4]);
y2 = parseInt(edgePath[5]);
}
}
else {
// Path is a string: "M,x1,y1,L,x2,y2"
var parts = edgePath.split(",");
x1 = parseInt(parts[1]);
y1 = parseInt(parts[2]);
x2 = parseInt(parts[4]);
y2 = parseInt(parts[5]);
}
return Math.sqrt(distanceToSegmentSquared(xPos, yPos, x1, y1, x2, y2));
}
};
this.getEdgeParam = function(id) {
/* return the parameters of an edge in table mode */
var vInfo = this.visualGraph.getEdgeVisualInfo(id);
var vertices = this.graph.getEdgeVertices(id);
var vertex1Pos = this.visualGraph.getVertexVisualInfo(vertices[0]);
var vertex2Pos = this.visualGraph.getVertexVisualInfo(vertices[1]);
var x1 = vertex1Pos.x;
var y1 = vertex1Pos.y;
var x2 = vertex2Pos.x;
var y2 = vertex2Pos.y;
var info = this.graph.getVertexInfo(vertices[0]);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vertex1Pos.wCorr);
var r = this.circleAttr.r;
var D = Math.sqrt(Math.pow((x2-x1),2) + Math.pow((y2-y1),2)); // distance between vertex1 and vertex2
var R = D*vInfo["radius-ratio"]; // arc radius, between D/2 and +inf (almost straight line at D*50).
var s = vInfo.sweep || 0; // sweep flag
var l = vInfo["large-arc"] || 0; // large arc flag
/* Calculation of the coordinates of the target point at the surface of the target vertex */
var angle = (l) ? (Math.asin(D/(2*R)) + Math.PI) : Math.asin(D/(2*R));
var alpha = this.getAngleBetween(x1,y1,x2,y2);
if(vertex1Pos.tableMode){
if(s){
var alpha1 = (l) ? -alpha - angle : angle - alpha;
}else{
var alpha1 = (l) ? angle - alpha : -alpha - angle;
}
var info1 = this.graph.getVertexInfo(vertices[0]);
var content1 = (info1.content) ? info1.content : "";
var boxSize1 = this.getBoxSize(content1,vertex1Pos.wCorr);
var delta = Math.PI - alpha1;
var pos1 = this.getSurfacePointFromAngle(x1,y1,boxSize1.w,boxSize1.h,delta);
}else{
var pos1 = { x: x1, y: y1 };
}
if(s){
alpha = (l) ? alpha - angle : alpha + angle;
}else{
alpha = (l) ? alpha + angle : alpha - angle;
}
var pos2 = this.getSurfacePointFromAngle(x2,y2,boxSize.w,boxSize.h,alpha);
var D2 = Math.sqrt(Math.pow((pos2.x-pos1.x),2) + Math.pow((pos2.y-pos1.y),2));
var R = D2*vInfo["radius-ratio"];
return {R: R, s:s, l:l, D: D2, pos1: pos1, pos2: pos2};
}
this.getLabelPos = function(edgeID,vertex1,vertex2) {
var edgeVertices = this.graph.getEdgeVertices(edgeID);
vertex1 = edgeVertices[0];
vertex2 = edgeVertices[1];
var info = this.graph.getEdgeInfo(edgeID);
var vInfo = this.visualGraph.getEdgeVisualInfo(edgeID);
var vertex1Pos = this.visualGraph.getVertexVisualInfo(vertex1);
var vertex2Pos = this.visualGraph.getVertexVisualInfo(vertex2);
var x1 = vertex1Pos.x;
var y1 = vertex1Pos.y;
var x2 = vertex2Pos.x;
var y2 = vertex2Pos.y;
var label = info.label || "";
var labelW = label.length * this.edgeLabelAttr["font-size"] || this.edgeLabelAttr["font-size"];
var labelH = this.edgeLabelAttr["font-size"];
var margin = 10;
if(label.length < 2){
labelW += margin;
}
var angle = this.getAngleBetween(x1,y1,x2,y2);
if(vInfo["radius-ratio"] || vertex1 === vertex2){ // if curved edge
if(vertex1 === vertex2){ // if loop
var R = this.circleAttr.r*vInfo["radius-ratio"];
if(vertex1Pos.tableMode){ // if table mode
angle = vInfo.angle*Math.PI/180 || 0;
angle = this.bindAngle(angle);
var info = this.graph.getVertexInfo(vertex1);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vertex1Pos.wCorr);
var beta = Math.atan(boxSize.h/boxSize.w); // angle between center of vertex and corner of box
var surfPos = this.getSurfacePointFromAngle(vertex1Pos.x,vertex1Pos.y,boxSize.w,boxSize.h,Math.PI - angle);
if(angle <= beta && angle > -beta){
/* right side */
var xm = surfPos.x + R*3/2;
var ym = surfPos.y;
}else if(angle <= Math.PI + beta && angle > Math.PI - beta){
/* left side */
// console.log('left');
var xm = surfPos.x - R*3/2;
var ym = surfPos.y;
}else if(angle > beta && angle <= Math.PI - beta){
/* top */
var xm = surfPos.x;
var ym = surfPos.y - R*3/2;
}else if(angle > Math.PI + beta || angle <= - beta){
/* bottom */
// console.log('bottom');
var xm = surfPos.x;
var ym = surfPos.y + R*3/2;
}
var x = xm - (labelW/2)*Math.sin(angle - Math.PI/2);
var y = ym + (labelH*1.5)*Math.cos(angle + Math.PI/2);
}else{
angle = vInfo.angle*Math.PI/180 || 0;
var xm = x1 + 2*R*Math.cos(angle);
var ym = y1 - 2*R*Math.sin(angle);
var x = xm - (labelW/2)*Math.sin(angle - Math.PI/2);
var y = ym + (labelH/2)*Math.cos(angle + Math.PI/2);
}
}else{
var s = vInfo["sweep"] || 0;
var l = vInfo["large-arc"] || 0;
var D = Math.sqrt(Math.pow((x2-x1),2) + Math.pow((y2-y1),2));
var R = D*vInfo["radius-ratio"];
if(vertex2Pos.tableMode){
var info = this.graph.getVertexInfo(vertex2);
var content = (info.content) ? info.content : "";
var boxSize = this.getBoxSize(content,vertex2Pos.wCorr);
var alpha = (l) ? (Math.asin(D/(2*R)) + Math.PI) : Math.asin(D/(2*R));
var angle2 = angle;
if(vertex1Pos.tableMode){
if(s){
var alpha1 = (l) ? -angle2 - alpha : alpha - angle2;
}else{
var alpha1 = (l) ? alpha - angle2 : -angle2 - alpha;
}
var info1 = this.graph.getVertexInfo(vertex1);
var content1 = (info1.content) ? info1.content : "";
var boxSize1 = this.getBoxSize(content1,vertex1Pos.wCorr);
var delta = Math.PI - alpha1;
var pos1 = this.getSurfacePointFromAngle(x1,y1,boxSize1.w,boxSize1.h,delta);
}else{
var pos1 = { x: x1, y: y1 };
}
if(s){
angle2 = (l) ? angle2 - alpha : angle2 + alpha;
}else{
angle2 = (l) ? angle2 + alpha : angle2 - alpha;
}
var pos2 = this.getSurfacePointFromAngle(x2,y2,boxSize.w,boxSize.h,angle2);
var D2 = Math.sqrt(Math.pow((pos2.x-pos1.x),2) + Math.pow((pos2.y-pos1.y),2));
var R = D2*vInfo["radius-ratio"];
var cPos = this.getCenterPosition(R,s,l,pos1,pos2);
}else{
var cPos = this.getCenterPosition(R,s,l,vertex1Pos,vertex2Pos);
}
if(vInfo["radius-ratio"] == 0.5){
R += 10;
}
var xm = (s) ? cPos.x + R*Math.sin(angle) : cPos.x - R*Math.sin(angle);
var ym = (s) ? cPos.y - R*Math.cos(angle) : cPos.y + R*Math.cos(angle);
var x = (s) ? xm + (labelW/2)*Math.sin(angle) : xm - (labelW/2)*Math.sin(angle);
var y = (s) ? ym - (labelH/2 + margin)*Math.cos(angle) : ym + (labelH/2 + margin)*Math.cos(angle);
}
}else{
var xm = (x2 + x1)/2;
var ym = (y2 + y1)/2;
var x = xm + (labelW/2)*Math.sin(angle);
var y = ym - (labelH/2 + margin)*Math.cos(angle);
}
return {x:x,y:y};
};
this.isOnEdgeLabel = function(edgeID,x,y) {
var edgeInfo = this.graph.getEdgeInfo(edgeID);
if(!edgeInfo.label || edgeInfo.label.length === 0){
return false;
}
var labelPos = this.getLabelPos(edgeID);
var fontSize = this.edgeLabelAttr["font-size"] || 15;
var labelH = fontSize;
var labelW = edgeInfo.label.length * fontSize || fontSize;
if(x < (labelPos.x + labelW/2) && x > (labelPos.x - labelW/2) && y < (labelPos.y + labelH/2) && y > (labelPos.y - labelH/2)){
return true;
}
return false;
};
this.getCenterPosition = function(R,s,l,vInfo1,vInfo2) {
var x1 = vInfo1.x, y1 = vInfo1.y;
var x2 = vInfo2.x, y2 = vInfo2.y;
// if(y1 !== y2){
if(y1 > (y2 + 1) || y1 < (y2 - 1)){ // to prevent divergence when y1 is very close to y2
// parameters of the bisection
var A = (x1 - x2)/(y2 - y1);
var B = (x2*x2 + y2*y2 - x1*x1 - y1*y1)/(2*(y2 - y1));
var a = 1 + A*A;
var b = 2*(A*B - A*y2 - x2);
var c = x2*x2 + y2*y2 + B*B - 2*y2*B - R*R;
var delta = b*b - 4*a*c;
if(delta <= 0)
delta = 0;
if(y1 > y2){
if(s){
var xc = (l) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}else{
var xc = (l) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}
}else{
if(s){
var xc = (l) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}else{
var xc = (l) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}
}
var yc = A*xc + B;
}else{
var xc = (x1 + x2)/2;
var a = 1;
var b = -2*y1;
var c = y1*y1 + xc*xc + x1*x1 - 2*xc*x1 - R*R;
var delta = b*b - 4*a*c;
if(x1 < x2){
if(s){
var yc = (l) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}else{
var yc = (l) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}
}else{
if(s){
var yc = (l) ? (-b + Math.sqrt(delta))/(2*a) : (-b - Math.sqrt(delta))/(2*a);
}else{
var yc = (l) ? (-b - Math.sqrt(delta))/(2*a) : (-b + Math.sqrt(delta))/(2*a);
}
}
}
return {x:xc,y:yc};
};
function distanceSquared(x1, y1, x2, y2) {
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}
function distanceToSegmentSquared(xPos, yPos, x1, y1, x2, y2) {
// Use algorithm for distance between point and segment.
// See: https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/1501725#1501725
var lengthSquared = distanceSquared(x1, y1, x2, y2);
if(lengthSquared === 0) {
return distanceSquared(xPos, yPos, x1, y1);
}
var t = ((xPos - x1) * (x2 - x1) + (yPos - y1) * (y2 - y1)) / lengthSquared;
t = Math.max(0, Math.min(1, t));
return distanceSquared(xPos, yPos, x1 + t * (x2 - x1), y1 + t * (y2 - y1));
}
this.getTextSize = function(text) {
var array = (text) ? text.split("\n") : [];
var nbLines = array.length;
var nbCol = 0;
for(var iLine = 0; iLine < nbLines; iLine++){
var line = array[iLine];
if(line.length > nbCol){
nbCol = line.length;
}
}
return { nbLines: nbLines, nbCol: nbCol };
};
this.bindAngle = function(angle) {
/* return angle between -PI/2 and 3PI/2 */
angle = angle%(2*Math.PI);
if(angle > 3*Math.PI/2){
angle -= 2*Math.PI;
}else if(angle < -Math.PI/2){
angle += 2*Math.PI;
}
return angle;
};
}