forked from Open-CT/openct-tasks
1226 lines
49 KiB
JavaScript
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;
|
|
};
|
|
}
|