Created
December 19, 2023 19:05
-
-
Save steveroush/21f1a85ea662c6bf880db26c93c1afcd to your computer and use it in GitHub Desktop.
Graphviz: fixOrtho - a work-around to allow ortho edges to connect to ports
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
neato complains about overlapping nodes, kicks our warning message (see below), | |
and uses splines=false. | |
"Warning: the bounding boxes of some nodes touch - falling back to straight | |
line edges" | |
- to do: | |
- multi-edge?? | |
how to use: | |
dot -Gsplines=true myFile.gv | | |
gvpr -cf fixOrtho.gvpr | | |
neato -n1 -Tpng -Gsplines=ortho >myFile.png | |
OR | |
dot -Gnodesep=.8 -Gsplines=true myFile.gv |gvpr -cf fixOrtho.gvpr | neato -Tpng -n1 -Gsplines=ortho myFile.png | |
options: -a S << will show new node(s) and edges in red | |
*/ | |
BEGIN{ | |
graph_t Root; | |
int nodeNo=0, show; | |
int HeadsTails[], changeTail[], changeHead[], ignoreEdge[]; | |
node_t newNode[]; | |
string tailPt[], headPt[], nextToTail[], nextToHead[], whichPts[], xxxx; | |
float M, B; // slope | |
////////////////////////////////////////////////////////////// | |
void doErr(string errString) { | |
print("// Error: ", errString); | |
printf(2,"Error: %s\n", errString); | |
} | |
////////////////////////////////////////////////////////////// | |
void doMsg(string errString) { | |
print("// Note: ", errString); | |
//printf(2,"Note: %s\n", errString); | |
} | |
///////////////////////////////////////////////////////////// | |
float Max(float f1, float f2) { | |
float fx; | |
if (f1>f2) | |
fx=f1; | |
else | |
fx=f2; | |
return fx; | |
} | |
///////////////////////////////////////////////////////////// | |
float Min(float f1, float f2) { | |
float fx; | |
if (f1>f2) | |
fx=f2; | |
else | |
fx=f1; | |
return fx; | |
} | |
///////////////////////////////////////////////////////////// | |
float abs(float f1) { | |
float fx; | |
if (f1<0) | |
fx=-f1; | |
else | |
fx=f1; | |
return fx; | |
} | |
///////////////////////////////////////////////////////////// | |
// greater than, with some fudge for floating point values | |
int greater(float f1, float f2) { | |
int rc; | |
if (abs(f1)-abs(f2)>.09) | |
rc=1; | |
else | |
rc=0; | |
print("// greater : ", f1, " ", f2, " ",rc); | |
return rc; | |
} | |
///////////////////////////////////////////////////////////// | |
// less than, with some fudge for floating point values | |
int less(float f1, float f2) { | |
int rc; | |
if (abs(f1)-abs(f2)<-.09) | |
rc=1; | |
else | |
rc=0; | |
print("// less : ", f1, " ", f2, " ",rc); | |
return rc; | |
} | |
///////////////////////////////////////////////////////////// | |
// equal to, with some fudge for floating point values | |
int equal(float f1, float f2) { | |
int rc; | |
if (!(less(f1, f2)) && !(greater(f1,f2))) | |
rc=1; | |
else | |
rc=0; | |
print("// equal : ", f1, " ", f2, " ",rc); | |
return rc; | |
} | |
////////////////////////////////////////////////////////////// | |
// compute distance in points - always positive value | |
float distance(float x1,float y1,float x2,float y2) { | |
float di; | |
di=sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); | |
return di; | |
} | |
////////////////////////////////////////////////////////////// | |
// compute distance in points - always positive value | |
float distancePtPt(string Pt1, string Pt2) { | |
float dist; | |
dist=distance((float)xOf(Pt1), (float)yOf(Pt1), (float)xOf(Pt2), (float)yOf(Pt2)); | |
return dist; | |
} | |
///////////////////////////////////////////////////////////// | |
string whichSide() { // cannot pass an array, so we will use global array, yuck | |
float whichPtX[], whichPtY[], whichDX, whichDY, tmpX, tmpY; | |
int fi; | |
string rstr; | |
// 1 & 2 index 2 corners of node | |
// 3 is index of landing pt other end of edge | |
// 4 & 5 are two alternatives for comptued end pt of pseudo-ortho segment | |
for (whichPts[fi]) { | |
sscanf (whichPts[fi], "%lf,%lf", &tmpX, &tmpY); | |
whichPtX[fi]=tmpX; | |
whichPtY[fi]=tmpY; | |
print("// whichPts : ", fi, " ",whichPts[fi], " ",whichPtX[fi]," --- ",whichPtY[fi]); | |
} | |
whichDX=whichPtX[1]-whichPtX[2]; | |
whichDY=whichPtY[1]-whichPtY[2]; | |
if (whichDX==0) | |
M=9999999; // fudge - no divide by 0 | |
else | |
M=whichDY/whichDX; | |
B=whichPtY[1]-(M*whichPtX[1]); | |
print("// equation M: ", M," B: ",B, " whichDX: ", whichDY); | |
if ((whichPtY[3] > ((M*whichPtX[3])+B) && whichPtY[4] > ((M*whichPtX[4])+B)) || | |
(whichPtY[3] < ((M*whichPtX[3])+B) && whichPtY[4] < ((M*whichPtX[4])+B)) || | |
(whichPtY[3] == ((M*whichPtX[3])+B) && whichPtY[4] == ((M*whichPtX[4])+B))) | |
rstr=whichPts[4]; | |
else | |
rstr=whichPts[5]; | |
unset (whichPtX); | |
print("// returning : ",rstr); | |
return rstr; | |
} | |
//////////////////////////////////////////////////// | |
node_t createNode(edge_t thisEdge, string HT, string portPt, string nextToPortPt, | |
int hasArrowhead, string otherEdgePt, string portStr) { | |
node_t TN, thisNode, otherNode, thisTail, thisHead; // are all these used?????? | |
float portPtX, portPtY, otherEndPtX, otherEndPtY, dx, dy; | |
float arrowSize, arrowX1, arrowX2, arrowY1, arrowY2; | |
float _left, _right, _top, _bottom, dAX, dAY, toGoX, toGoY; | |
float _centerX, _centerY, tmpPtX, tmpPtY; | |
string P, center; | |
float marg; | |
/////// marg, seemingly minimum of 4, look at pmargin in neatosplines.c | |
marg=6; | |
if (HT=="T") { | |
thisNode=thisEdge.tail; | |
otherNode=thisEdge.head; | |
} else { | |
thisNode=thisEdge.head; | |
otherNode=thisEdge.tail; | |
} | |
center=thisNode.pos; | |
print("// START Createnode: ", thisNode, " : ", otherNode, " : ", portPt, " : ", nextToPortPt, " : ",otherEdgePt, " : ", portStr); | |
//////////////////////////////////////////////// | |
// | |
// should we drop this test ??? (newNode[portPt]!=NULL) | |
// | |
//////////////////////////////////////////////// | |
if (newNode[portPt]!=NULL) { | |
TN=newNode[portPt]; | |
} else { | |
arrowX1=(float)xOf(portPt); | |
arrowY1=(float)yOf(portPt); | |
arrowX2=(float)xOf(nextToPortPt); | |
arrowY2=(float)yOf(nextToPortPt); | |
if (hasArrowhead) | |
arrowSize=distance(arrowX1, arrowY1, arrowX2, arrowY2); | |
else | |
arrowSize=0; | |
print("// arrowsize: ", portPt, " -- ", nextToPortPt, " -- ", arrowSize); | |
_centerX=(float)xOf(thisNode.pos); | |
_centerY=(float)yOf(thisNode.pos); | |
_left=_centerX-((float)thisNode.width*72./2.); | |
_right=_centerX+((float)thisNode.width*72./2.); | |
_bottom=_centerY-((float)thisNode.height*72./2.); | |
_top=_centerY+((float)thisNode.height*72./2.); | |
marg+=arrowSize; | |
if (arrowSize==0) | |
arrowSize=4; | |
portPtX=(float)xOf(portPt); | |
portPtY=(float)yOf(portPt); | |
otherEndPtX=(float)xOf(otherEdgePt); | |
otherEndPtY=(float)yOf(otherEdgePt); | |
if (HT=="T") { | |
dx=otherEndPtX-portPtX; | |
dy=otherEndPtY-portPtY; | |
} else { | |
dx=portPtX-otherEndPtX; | |
dy=portPtY-otherEndPtY; | |
} | |
P=sub(portStr,"*:",""); | |
print("// createnode (switch): ", portStr," ",P, " dx: ", dx," dy: ",dy); | |
whichPts[1]=portPt; | |
whichPts[3]=otherEdgePt; | |
switch(portStr) { | |
case "n": | |
xxxx=(string)(portPtX) + "," + (string)(portPtY+marg); | |
break; | |
case "s": | |
xxxx=(string)(portPtX) + "," + (string)(portPtY-marg); | |
break; | |
case "e": | |
xxxx=(string)(portPtX+marg) + "," + (string)(portPtY); | |
break; | |
case "w": | |
xxxx=(string)(portPtX-marg) + "," + (string)(portPtY); | |
break; | |
case "sw": | |
whichPts[2]=(string)_right + "," + (string)_top; // opposite corner | |
whichPts[4]=(string)(portPtX -marg) + "," + (string)(portPtY + 0 ); | |
whichPts[5]=(string)(portPtX +0 ) + "," + (string)(portPtY - marg); | |
xxxx=whichSide(); | |
break; | |
case "se": | |
whichPts[2]=(string)_left + "," + (string)_top; // opposite corner | |
whichPts[4]=(string)(portPtX +marg) + "," + (string)(portPtY + 0 ); | |
whichPts[5]=(string)(portPtX +0 ) + "," + (string)(portPtY - marg); | |
xxxx=whichSide(); | |
break; | |
case "nw": | |
whichPts[2]=(string)_right + "," + (string)_bottom; // opposite corner | |
whichPts[4]=(string)(portPtX -marg) + "," + (string)(portPtY + 0 ); | |
whichPts[5]=(string)(portPtX +0 ) + "," + (string)(portPtY + marg); | |
xxxx=whichSide(); | |
break; | |
case "ne": | |
whichPts[2]=(string)_left + "," + (string)_bottom; // opposite corner | |
whichPts[4]=(string)(portPtX +marg) + "," + (string)(portPtY + 0 ); | |
whichPts[5]=(string)(portPtX +0 ) + "," + (string)(portPtY + marg); | |
xxxx=whichSide(); | |
break; | |
default: { // c (center) or record/html port name | |
print("// corner compass point OR INSIDE node or RECORD port"); | |
print("// _left, _right, _top, _bottom : ", _left, " : ", _right, " : ", _top, " : ", _bottom); | |
print("// portPtX, portPtY : ", portPtX, " : ", portPtY) ; | |
tmpPtX=portPtX; | |
tmpPtY=portPtY; | |
if (P=="@(ne|nw|se|sw)" || less(_left,tmpPtX) && greater(_right,tmpPtX) && less(_bottom,tmpPtY) && greater(_top,tmpPtY)) { | |
// inside the node | |
print("// INSIDE a node OR compass corner"); | |
print("// arrowX1, arrowX2, arrowY1, arrowY2: ", arrowX1, " - ", arrowX2, " - ", arrowY1," - ", arrowY2); | |
dAX=abs(arrowX1-arrowX2); | |
dAY=abs(arrowY1-arrowY2); | |
dx=0; | |
dy=0; | |
if (dAY>dAX) { // go vertical | |
print("// vertical: ", dAX," ",dAY); | |
if (tmpPtY>_top || tmpPtY<_bottom) | |
toGoY=0; | |
else if (tmpPtY>_centerY) | |
toGoY=_top-tmpPtY; | |
else | |
toGoY=_bottom-tmpPtY; | |
dy=marg+Max(arrowSize, abs(toGoY)); | |
print("// toGoY : ",toGoY, " dy : ",dy); | |
if (tmpPtY<_centerY) | |
dy=-dy; | |
tmpPtY+=dy; | |
} else { | |
print("// horizontal: ", dAX," ",dAY); | |
if (tmpPtX>_right || tmpPtX<_left) | |
toGoX=0; | |
else if (tmpPtX>_centerX) | |
toGoX=_right-tmpPtX; | |
else | |
toGoX=_left-tmpPtX; | |
dx=marg+Max(arrowSize, abs(toGoX)); | |
print("// toGoX : ",toGoX, " dx : ",dx); | |
if (tmpPtX<_centerX) | |
dx=-dx; | |
tmpPtX+=dx; | |
} | |
} else { | |
// not compass based, but touching a side of the node | |
// (maybe two sides, but we will ignore that for now) | |
print("// Touching a Side"); | |
if (equal(_left,tmpPtX)) { | |
tmpPtX-=marg; | |
} else if (equal(_right,tmpPtX)) { | |
tmpPtX+=marg; | |
} else if (equal(_bottom,tmpPtY)) { | |
tmpPtY-=marg; | |
} else if (equal(_top,tmpPtY)) { | |
tmpPtY+=marg; | |
} | |
} | |
xxxx=(string)(tmpPtX) + "," + (string)(tmpPtY); | |
break; | |
} | |
} | |
TN=node(Root, "__portNode__" + thisNode.name + "_"+ (string)++nodeNo); | |
TN.shape="point"; | |
TN.label=""; | |
TN.savePos=portPt; | |
TN.pos=xxxx; | |
print("// createnode portPtX & portPtY: ",portPtX," ",portPtY," pos: ", TN.pos," marg: ",marg); | |
if (show) { | |
TN.color="red"; | |
} else { | |
if (hasAttr(thisEdge, "color")) | |
TN.color=thisEdge.color; | |
TN.width=0; | |
TN.height=0; | |
} | |
newNode[portPt]=TN; | |
} | |
return TN; | |
} | |
//////////////////////////////////////////////////// | |
} | |
BEG_G{ | |
int i, cnt, Head, Tail, new=0, hasTailArrow[], hasHeadArrow[]; | |
node_t aN, tN, hN; | |
string eString, point[int]; | |
string Dir1[], Dir2[], DIR; | |
edge_t oldE, newE, workE; | |
node_t tmpN; | |
int deleteIt[]; | |
Root=$G; | |
if ($G.directed==1) | |
eString="->"; | |
else | |
eString="--"; | |
if (ARGC ==1 && ARGV[0]=="[sS]") | |
show=1; | |
else | |
show=0; | |
Dir1[""]="none"; | |
Dir2[""]="forward"; | |
Dir1["forward"]="none"; | |
Dir2["forward"]="forward"; | |
Dir1["back"]="back"; | |
Dir2["back"]="none"; | |
Dir1["none"]="none"; | |
Dir2["none"]="none"; | |
Dir1["both"]="back"; | |
Dir2["both"]="forward"; | |
} | |
E{ | |
int needNewEdges=0; | |
string s; | |
if (ignoreEdge[$]==1) | |
continue; | |
unset(point); | |
print("// EDGE : ", $.name); | |
print("// pos: ", $.pos); | |
cnt=tokens($.pos, point); | |
/* | |
does not handle multiedges(?) (pos=spline ; spline ..." | |
*/ | |
Tail=0; | |
Head=cnt-1; | |
for (i=0; i<cnt; i++) { | |
print("// point: ",i," ",point[i]); | |
s=point[i]; | |
// arrowheads? | |
if (point[i]=="[se]*") { | |
print("// arrowhead : ",point[i]); | |
s=substr(point[i],2); | |
if (point[i]=="s*") { | |
Tail=i; | |
hasTailArrow[$]=1; | |
print("// Tail: ",Tail," Head: ", Head); | |
print("// POINT: ", point[i]); | |
point[i]=s; | |
print("// POINT: ", point[i]); | |
} else if (point[i]=="e*") { | |
Head=i; | |
hasHeadArrow[$]=1; | |
// if no tail (but head), set to 1 | |
if (Tail==Head) { | |
Tail++; | |
} | |
print("// Head: ", Head, " Tail: ",Tail); | |
print("// POINT: ", point[i]); | |
point[i]=s; | |
print("// POINT: ", point[i]); | |
} | |
} | |
} | |
tailPt[$]=point[Tail]; | |
headPt[$]=point[Head]; | |
if (hasTailArrow[$]==1) { | |
if (hasHeadArrow[$]==1) { // both | |
nextToTail[$]=point[Head+1]; | |
nextToHead[$]=point[cnt-1]; | |
} else { // back | |
nextToTail[$]=point[Tail+1]; | |
nextToHead[$]=point[Head-1]; | |
} | |
} else { | |
if (hasHeadArrow[$]==1) { // forward | |
nextToTail[$]=point[Tail+1]; | |
nextToHead[$]=point[cnt-1]; | |
} else { // none | |
nextToTail[$]=point[Tail+1]; | |
nextToHead[$]=point[cnt-2]; | |
} | |
} | |
tN=$.tail; | |
hN=$.head; | |
print("// edge: ", $.name); | |
if (hasAttr($, "tailport") && $.tailport!="") { | |
print("// tailport: ", $.tailport, ", ", point[Tail], " next: ", nextToTail[$]); | |
changeTail[$]=1; | |
HeadsTails[$]++; | |
needNewEdges=1; | |
} | |
if (hasAttr($, "headport") && $.headport!="") { | |
print("// headport: ", $.headport, ", ", point[Head], " next: ", nextToHead[$]); | |
changeHead[$]=1; | |
HeadsTails[$]++; | |
needNewEdges=1; | |
} | |
// if (needNewEdges==0) | |
// $.pin="true"; | |
workE=$; | |
oldE=$; | |
if (hasAttr($, "dir")) | |
DIR=$.dir; | |
else | |
DIR=""; | |
if (changeTail[oldE]) { | |
print("// changeTAIL - ",oldE.name); | |
tmpN=createNode(workE, "T", tailPt[oldE], nextToTail[oldE], hasTailArrow[oldE], headPt[oldE], workE.tailport); | |
newE=edge(workE.tail, tmpN, ""); | |
ignoreEdge[newE]=1; | |
copyA(workE, newE); | |
newE.headport=""; | |
newE.dir=Dir1[DIR]; | |
newE.pin="true"; | |
if (show) { | |
newE.color="red"; | |
} | |
////// edge #2 (reduced version of workE) | |
newE=edge(tmpN, workE.head, ""); | |
ignoreEdge[newE]=1; | |
copyA(workE, newE); | |
newE.tailport=""; | |
newE.dir=Dir2[DIR]; | |
// fix certain attributes ?? | |
deleteIt[workE]=1; | |
workE=newE; // if changeHead also, use this (new) edge | |
} | |
/******** | |
if headport, we are creating a newnode->newnode loop edge, | |
in addition to correct edges | |
*******/ | |
if (changeHead[oldE]) { | |
print("// changeHEAD - ",oldE.name); | |
tmpN=createNode(workE, "H", headPt[oldE], nextToHead[oldE], hasHeadArrow[oldE], tailPt[oldE], workE.headport); | |
newE=edge(tmpN, workE.head, ""); | |
ignoreEdge[newE]=1; | |
copyA(workE, newE); | |
newE.tailport=""; | |
newE.dir=Dir2[DIR]; | |
newE.pin="true"; | |
if (show) { | |
newE.color="red"; | |
} | |
////// edge #2 (reduced version of workE) | |
newE=edge(workE.tail, tmpN, ""); | |
ignoreEdge[newE]=1; | |
copyA(workE, newE); | |
newE.headport=""; | |
newE.dir=Dir1[DIR]; | |
// fix certain attributes ?? | |
deleteIt[workE]=1; | |
} | |
} | |
END_G{ | |
print("////////////////////////// END_G"); | |
for (deleteIt[oldE]) { | |
delete(Root, oldE); | |
} | |
$G.splines="ortho"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The ortho edge implementation does not handle ports (see https://forum.graphviz.org/t/regarding-graphvizs-orthogonal-edge-routing/1889/2, https://gitlab.com/graphviz/graphviz/-/issues/352, or https://gitlab.com/graphviz/graphviz/-/issues/1415)
Here is a link (https://gist.github.com/steveroush/21f1a85ea662c6bf880db26c93c1afcd) to a gvpr program that will often allow ortho edges to connect to ports.
It is easy, but not trivial to use:
dot -Gsplines=true
myFile.gv > myFile.dot` (This will position all the nodes and ports)gvpr -cf fixOrtho.gvpr myFile.dot >myfileFixed.dot
(This adds nodes and edges and alters existing edges)neato -n -Tpng myFileFixed.dot >myfileFixed.png
(Creates viewable image)Note: the commands above can also be run as a single pipeline.
This is modestly tested software (surprisingly complex). Please try it and comment here. If/when it settles down, it can become part of the Graphviz package it that is desired, though I consider this a temporary "fix".
Here are three versions of the ports.gv file that is included in the Graphviz source. (The red edge components are just to show the changes)