text: 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 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) {
        if(!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;
     };
  }
  