-
-
Save steveroush/21f1a85ea662c6bf880db26c93c1afcd to your computer and use it in GitHub Desktop.
/***************************************************** | |
Name: fixOrtho.gvpr | |
As of Date: 2024-08-25 20:42:37 | |
Description: a gvpr program that attempts to allow ortho edges connectiongports on nodes | |
(currently the splines=ortho code does NOT correctlt draw edges to/from ports) | |
Usage: dot -Gsplines=true myFile.gv |gvpr -cf fixOrtho.gvpr | neato -n2 -Gsplines=ortho -T... | |
Arguments: -a S [color] << will show new node(s) and edges in green or optional color | |
*****************************************************/ | |
/*************************************************************************************** | |
how to use: | |
dot -Gsplines=true myFile.gv | | |
gvpr -cf fixOrtho.gvpr | | |
neato -n2 -Tpng -Gsplines=ortho >myFile.png | |
OR | |
dot -Gnodesep=.8 -Gsplines=true myFile.gv |gvpr -cf fixOrtho.gvpr | neato -Tpng -n2 -Gsplines=ortho myFile.png | |
options: -a S [color] << will show new node(s) and edges in green or optional color | |
**************************************************************************************/ | |
BEGIN{ | |
graph_t Root; | |
node_t newNode[]; | |
int show, debug; | |
int HeadsTails[], changeTail[], changeHead[], ignoreEdge[]; | |
float M, B; | |
float arrowSize; | |
string tailPt[], headPt[], nextToTail[], nextToHead[], whichPts[], posStr, showColor; | |
string help ; | |
help=" | |
how to use: | |
dot -Gsplines=true myFile.gv | | |
gvpr -cf fixOrtho.gvpr | | |
neato -n2 -Tpng -Gsplines=ortho >myFile.png | |
OR | |
dot -Gnodesep=.8 -Gsplines=true myFile.gv |gvpr -cf fixOrtho.gvpr | neato -Tpng -n2 -Gsplines=ortho myFile.png | |
options: | |
-a S [color] << will show new node(s) and edges in green or optional color | |
"; | |
////////////////////////////////////////////////////////////// | |
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 from one X,Y point to another - 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; | |
} | |
//////////////////////////////////////////////////////////////// | |
float XdistancePtPt(string Pt1, string Pt2) { | |
float dist; | |
dist=(float)xOf(Pt1) - (float)xOf(Pt2); | |
//dist=abs(dist); | |
return dist; | |
} | |
//////////////////////////////////////////////////////////////// | |
float YdistancePtPt(string Pt1, string Pt2) { | |
float dist; | |
dist=(float)yOf(Pt1) - (float)yOf(Pt2); | |
//dist=abs(dist); | |
return dist; | |
} | |
////////////////////////////////////////////////////////////// | |
int isHorizontalOrVertical(string Pt1, string Pt2) { | |
int rcode; | |
rcode=0; | |
if (xOf(Pt1)==xOf(Pt2) || yOf(Pt1)==yOf(Pt2)) | |
rcode=1; | |
return(rcode); | |
} | |
////////////////////////////////////////////////////////////// | |
// compute new pos, distance D (in points) from one X,Y point to another | |
string computePos(string Pt1, string Pt2, float D) { | |
float fullDist, frac, newX, newY; | |
string rslt; | |
print("// computePos: ",Pt1," ", Pt2, " ", D); | |
fullDist=distance((float)xOf(Pt1), (float)yOf(Pt1), (float)xOf(Pt2), (float)yOf(Pt2)); | |
if (fullDist==0) | |
frac=1; | |
else | |
frac=D/fullDist; | |
newX=(float)xOf(Pt1) + (frac* ((float)xOf(Pt2)-(float)xOf(Pt1))); | |
newY=(float)yOf(Pt1) + (frac* ((float)yOf(Pt2)-(float)yOf(Pt1))); | |
rslt=(string)newX + "," + (string)newY; | |
print("// computePos: ",Pt1," ", Pt2, " ", D," rslt:", rslt); | |
return rslt; | |
} | |
//////////////////////////////////////////////////////////////// | |
// chooses a point on the same side of a line as another point | |
string whichSide(string lnPt1, string lnPt2, string refPt, string optPt1, string optPt2) { | |
float whichPtX[], whichPtY[], whichDX, whichDY, tmpX, tmpY; | |
int fi; | |
string rstr; | |
// lnPt1 & lnPt2 two points on a line | |
// refPt a point on one side or the other of the above line | |
// optPt1 & optPt2 are two alternative points, one on each side | |
// return optPt1 or optPt2, whichever is on same side as whichPts[3] | |
whichPts[1]=lnPt1; | |
whichPts[2]=lnPt2; | |
whichPts[3]=refPt; | |
whichPts[4]=optPt1; | |
whichPts[5]=optPt2; | |
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]; | |
// calculat slope (M) | |
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=optPt1; | |
else | |
rstr=optPt2; | |
unset (whichPtX); | |
print("// returning : ",rstr); | |
return rstr; | |
} | |
//////////////////////////////////////////////////////////////////////// | |
string pickOrtho(string lnPt1, string lnPt2, string refPt, string ret) { | |
string optPt1, optPt2; | |
optPt1=xOf(lnPt1) + "," + yOf(lnPt2); | |
optPt2=xOf(lnPt2) + "," + yOf(lnPt1); | |
ret=whichSide(lnPt1,lnPt2,refPt,optPt1,optPt2); | |
print("// pick: ", ret); | |
return ret; | |
} | |
//////////////////////////////////////////////////////////////////////// | |
edge_t addEdge(node_t tailN, node_t headN, edge_t oldE, string newPos, string newDir, | |
string newLabel, string newTailport, string newHeadport) { | |
// if newPos=="" then neato/ortho will determine the pos | |
edge_t newE; | |
newE=isEdge(tailN, headN, ""); | |
if (newE==NULL){ | |
newE=edge(tailN, headN, ""); | |
print("// EDGE CREATED: ", newE.name); | |
copyA(oldE, newE); | |
newE.pos=newPos; | |
newE.dir=newDir; | |
if (newLabel != ">^leave^<") | |
newE.label=newLabel; | |
}else{ | |
print("// EDGE EXISTS: ", newE.name); | |
} | |
return newE; | |
} | |
//////////////////////////////////////////////////////////////////////// | |
node_t createSimpleNode(string newPos) { | |
int ONindx; | |
node_t TN; | |
TN=node(Root, "__OrthoNode__" + (string)++ONindx); | |
TN.pos=newPos; | |
TN.shape="point"; | |
TN.width=".02"; | |
TN.height=".02"; | |
TN.label=""; | |
TN.style="filled"; | |
TN.color="green"; | |
print("// new node: ", TN.name," ", TN.pos); | |
return TN; | |
} | |
//////////////////////////////////////////////////////////////////////// | |
// create a new node, near the head/tail landing place | |
////////////////////////////////////////////////////////////////////////// | |
node_t createEdgeNode(edge_t thisEdge, string HT, string portPt, string nextToPortPt, | |
int hasArrowhead, string otherEdgePt, string portStr) { | |
node_t TN, thisNode, otherNode; // are all these used?????? | |
float portPtX, portPtY, otherEndPtX, otherEndPtY, dx, dy; | |
float arrowX1, arrowX2, arrowY1, arrowY2; | |
float _left, _right, _top, _bottom, dAX, dAY, toGoX, toGoY; | |
float _centerX, _centerY, tmpPtX, tmpPtY, marg; | |
string P, center, newName; | |
string linePt1,linePt2,referencePt,choicePt1,choicePt2; | |
/////// 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 ",HT, " createEdgeNode: ", thisNode, " : ", otherNode, " : ", portPt, " : ", nextToPortPt, " : ",otherEdgePt, " : ", hasArrowhead, " : ", portStr); | |
portPt=sub(portPt,"[es],"); | |
nextToPortPt=sub(nextToPortPt,"[es],"); | |
print("// START ",HT, " createEdgeNode: ", thisNode, " : ", otherNode, " : ", portPt, " : ", nextToPortPt, " : ",otherEdgePt, " : ", hasArrowhead, " : ", portStr); | |
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=distancePtPt(portPt,nextToPortPt); | |
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("// createEdgeNode (before switch): ", portStr," ",P, " dx: ", dx," dy: ",dy); | |
linePt1=portPt; | |
referencePt=otherEdgePt; | |
switch(portStr) { | |
case "n": | |
posStr=(string)(portPtX) + "," + (string)(portPtY+marg); | |
break; | |
case "s": | |
posStr=(string)(portPtX) + "," + (string)(portPtY-marg); | |
break; | |
case "e": | |
posStr=(string)(portPtX+marg) + "," + (string)(portPtY); | |
break; | |
case "w": | |
posStr=(string)(portPtX-marg) + "," + (string)(portPtY); | |
break; | |
case "sw": | |
linePt2=(string)_right + "," + (string)_top; // opposite corner | |
choicePt1=(string)(portPtX -marg) + "," + (string)(portPtY + 0 ); | |
choicePt2=(string)(portPtX +0 ) + "," + (string)(portPtY - marg); | |
posStr=whichSide(linePt1,linePt2,referencePt,choicePt1,choicePt2); | |
break; | |
case "se": | |
linePt2=(string)_left + "," + (string)_top; // opposite corner | |
choicePt1=(string)(portPtX +marg) + "," + (string)(portPtY + 0 ); | |
choicePt2=(string)(portPtX +0 ) + "," + (string)(portPtY - marg); | |
posStr=whichSide(linePt1,linePt2,referencePt,choicePt1,choicePt2); | |
break; | |
case "nw": | |
linePt2=(string)_right + "," + (string)_bottom; // opposite corner | |
choicePt1=(string)(portPtX -marg) + "," + (string)(portPtY + 0 ); | |
choicePt2=(string)(portPtX +0 ) + "," + (string)(portPtY + marg); | |
posStr=whichSide(linePt1,linePt2,referencePt,choicePt1,choicePt2); | |
break; | |
case "ne": | |
linePt2=(string)_left + "," + (string)_bottom; // opposite corner | |
choicePt1=(string)(portPtX +marg) + "," + (string)(portPtY + 0 ); | |
choicePt2=(string)(portPtX +0 ) + "," + (string)(portPtY + marg); | |
posStr=whichSide(linePt1,linePt2,referencePt,choicePt1,choicePt2); | |
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; | |
} | |
} | |
posStr=(string)(tmpPtX) + "," + (string)(tmpPtY); | |
break; | |
} | |
} | |
// if node exists, we will use it | |
newName="__" + thisNode.name + "_" + portStr; | |
TN=isNode(Root, newName); | |
if (TN==NULL) { | |
TN=node(Root, newName); | |
TN.shape="point"; | |
TN.width=".02"; | |
TN.height=".02"; | |
TN.label=""; | |
TN.savePos=portPt; | |
TN.pos=posStr; | |
print("// createnode portPtX & portPtY: ",portPtX," ",portPtY," pos: ", TN.pos," marg: ",marg); | |
if (show) { | |
TN.color=showColor; | |
} else { | |
if (hasAttr(thisEdge, "color")) | |
TN.color=thisEdge.color; | |
TN.width=0; | |
TN.height=0; | |
} | |
newNode[portPt]=TN; | |
} | |
} | |
print("// returning: ", TN.name); | |
return TN; | |
} | |
//////////////////////////////////////////////////// | |
} | |
BEG_G{ | |
int i, cnt, firstPt, lastPt, HeadArrow, TailArrow, oneCurve; | |
int hasTailArrow[], hasHeadArrow[], deleteIt[]; | |
edge_t oldE, newE, workE; | |
node_t tmpN; | |
string eString, tmpStr, tmpStr2, point[int]; | |
string DirTail[], DirHead[]; | |
Root=$G; | |
if ($G.directed==1) | |
eString="->"; | |
else | |
eString="--"; | |
show=0; | |
showColor="green"; | |
debug=0; | |
i=0; | |
while (i<ARGC) { | |
if (show==1) { // kludge | |
showColor=ARGV[i]; | |
} else if (ARGV[i]=="[H?]") { // help | |
printf(2,help); | |
exit(0); | |
} else if (ARGV[i]=="[sS]") { | |
show=1; | |
} else if (ARGV[i]=="[sS]*") { | |
show=1; | |
showColor=substr(ARGV[i],1); | |
} else if (ARGV[i]=="D") { | |
debug=1; | |
show=1; | |
} else { | |
printf(2, help); | |
printf(2,"ERROR"); | |
} | |
print("// show: ",show," showColor: ", showColor); | |
i++; | |
} | |
DirTail[""]="none"; | |
DirHead[""]="forward"; | |
DirTail["forward"]="none"; | |
DirHead["forward"]="forward"; | |
DirTail["back"]="back"; | |
DirHead["back"]="none"; | |
DirTail["none"]="none"; | |
DirHead["none"]="none"; | |
DirTail["both"]="back"; | |
DirHead["both"]="forward"; | |
} | |
/////////////////////////////////////////////////////////////////////////////////////////// | |
E{ | |
int needNewEdges=0, dirType; | |
string s, dirStr, saveHeadLabel, saveTailLabel; | |
unset(point); | |
print("//////////////////////////////////////////"); | |
print("// EDGE : ", $.name); | |
if (ignoreEdge[$]==1) { | |
print("// ignoreEdge (continue)"); | |
continue; | |
} | |
ignoreEdge[$]=1; | |
print("// EDGE : ", $.name); | |
print("// pos: ", $.pos); | |
// there is a bug in dot (dot output format only), sometimes it messes up the pos value | |
// https://gitlab.com/graphviz/graphviz/-/issues/2439 | |
// it produces pos="s,... instead of pos="e,... | |
// OR | |
// it produces pos="e,... instead of pos="s,... | |
if (!hasAttr($,"dir") || $.dir=="" || $.dir=="forward") | |
dirType=1; | |
else | |
switch($.dir) { | |
case "forward": | |
dirType=1; | |
break; | |
case "back": | |
dirType=2; | |
break; | |
case "both": | |
dirType=3; | |
break; | |
case "none": | |
dirType=4; | |
break; | |
default: | |
MSG="Edge " + $.name + ", bad value for dir (" + $.dir + ")"; | |
doErr(MSG); | |
break; | |
} | |
print("// dirType: ",dirType); | |
cnt=tokens($.pos, point); | |
if (cnt<7) | |
oneCurve=1; | |
else | |
oneCurve=0; | |
/**************************************************** | |
now we try to "fix" the dot pos bug (see above) | |
do we need to check ""both"" ????????? | |
copied from alterSimpleEdge.gvpr !!!!!!!!!!!!! | |
*****************************************************/ | |
if ((point[0]=="s,*" && dirType==1) || (point[0]=="e,*" && dirType==2)) { | |
string ts[]; | |
int ti; | |
for (ti=1; ti<cnt; ti++) { | |
ts[ti]=point[cnt-ti]; | |
} | |
for (ti=1; ti<cnt; ti++) { | |
point[ti]=ts[ti]; | |
print("// ti: ", ti," point: ", point[ti]); | |
} | |
print("// before: >",point[0]); | |
if (point[0]=="s,*" && dirType==1) | |
point[0]="e," + substr(point[0],2); | |
else | |
point[0]="s," + substr(point[0],2); | |
print("// after: >",point[0]); | |
print("// FIXED backwards arrowhead"); | |
} | |
if (!((hasAttr($, "tailport") && $.tailport!="") || (hasAttr($, "headport") && $.headport!=""))) { | |
print("// No ports (continue)"); | |
continue; | |
} | |
TailArrow=-1; | |
HeadArrow=-1; | |
firstPt=-1; | |
lastPt=-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*") { | |
TailArrow=i; | |
hasTailArrow[$]=1; | |
print("// TailArrow: ",TailArrow," HeadArrow: ", HeadArrow); | |
print("// tail POINT: ", point[i]); | |
print("// tail POINT: ", point[i]); | |
} else if (point[i]=="e*") { | |
HeadArrow=i; | |
hasHeadArrow[$]=1; | |
print("// HeadArrow: ", HeadArrow, " TailArrow: ",TailArrow); | |
print("// head POINT: ", point[i]); | |
print("// head POINT: ", point[i]); | |
} | |
} else { | |
if (firstPt<0) | |
firstPt=i; | |
lastPt=i; | |
} | |
} | |
///////////////////////////////////////////////////////////////////////// | |
// this seems to work nicely | |
if (isHorizontalOrVertical(point[firstPt], point[lastPt])) { | |
string TS; | |
$.oldPos=$.pos; | |
TS=""; | |
if (TailArrow>=0) | |
TS=TS + " " + point[TailArrow]; | |
if (HeadArrow>=0) | |
TS=TS + " " + point[HeadArrow]; | |
TS=TS + " " + point[firstPt] + " " + point[firstPt] + " " + point[lastPt] + " " + point[lastPt]; | |
$.pos=TS; | |
if (hasAttr($, "tailport") && $.tailport!="") { | |
$.oldTailport=$.tailport; | |
$.tailport=""; | |
} | |
if (hasAttr($, "headport") && $.headport!="") { | |
$.oldHeadport=$.headport; | |
$.headport=""; | |
} | |
print("// ", $.name, " is already horizontal or vertical, go with it"); | |
continue; | |
} | |
///////////////////////////////////////////////////////////////////////// | |
// temporary - not using onecurve | |
if (oneCurve ) { | |
string pt; | |
print ("// One curve!!"); | |
/**************************************************************** | |
pick/find the one ortho point that we will use | |
we will create the complete pseudo-ortho edge, not just 2 partials | |
NOTE, oneCurve is sometimes 2 half-curves (S-shape) | |
we need to handle that (I assume) | |
***************************************************************/ | |
pt=pickOrtho(point[firstPt], point[lastPt], point[firstPt+1]); | |
// now create the new node at pt | |
// then add the two straight line segments | |
// do not forget any/all arrowheads | |
} | |
if (TailArrow>=0) | |
tailPt[$]=point[TailArrow]; | |
else | |
tailPt[$]=point[firstPt]; | |
if (HeadArrow>=0) | |
headPt[$]=point[HeadArrow]; | |
else | |
headPt[$]=point[lastPt]; | |
if (hasTailArrow[$]==1) { | |
if (hasHeadArrow[$]==1) { // both | |
nextToTail[$]=point[HeadArrow+1]; | |
nextToHead[$]=point[cnt-1]; | |
} else { // back | |
nextToTail[$]=point[TailArrow+1]; | |
nextToHead[$]=point[HeadArrow-1]; | |
} | |
} else { | |
if (hasHeadArrow[$]==1) { // forward | |
nextToTail[$]=point[TailArrow+1]; | |
nextToHead[$]=point[cnt-1]; | |
} else { // none | |
nextToTail[$]=point[TailArrow+1]; | |
nextToHead[$]=point[cnt-2]; | |
} | |
} | |
print("// edge: ", $.name); | |
if (hasAttr($, "tailport") && $.tailport!="") { | |
print("// BINGO tailport: ", $.tailport, ", ", point[TailArrow], " next: ", nextToTail[$]); | |
changeTail[$]=1; | |
HeadsTails[$]++; | |
needNewEdges=1; | |
} | |
if (hasAttr($, "headport") && $.headport!="") { | |
print("// BINGO headport: ", $.headport, ", ", point[HeadArrow], " next: ", nextToHead[$]); | |
changeHead[$]=1; | |
HeadsTails[$]++; | |
needNewEdges=1; | |
} | |
// remove non-ortho pos strings | |
$.splinedPos=$.pos; | |
if(debug==0) | |
$.pos=""; | |
////////////////////////////////////////////// | |
if (hasAttr($, "tailport") && $.tailport!="") { | |
$.oldTailport=$.tailport; | |
$.tailport=""; | |
} | |
if (hasAttr($, "headport") && $.headport!="") { | |
$.oldHeadport=$.headport; | |
$.headport=""; | |
} | |
// fiddle w/ headlabel, taillabel, and label | |
if (hasAttr($, "taillabel")) { | |
saveTailLabel=$.taillabel; | |
$.taillabel=""; | |
} | |
if (hasAttr($, "headlabel")) { | |
saveHeadLabel=$.headlabel; | |
$.headlabel=""; | |
} | |
$.label=""; | |
$.lp=""; | |
workE=$; | |
oldE=$; | |
if (hasAttr($, "dir")) | |
dirStr=$.dir; | |
else | |
dirStr=""; | |
if (changeTail[oldE]) { | |
print("// changeTAIL - ",oldE.name); | |
tmpN=createEdgeNode(workE, "T", tailPt[oldE], nextToTail[oldE], hasTailArrow[oldE], headPt[oldE], workE.oldTailport); | |
tmpStr=""; | |
if (TailArrow>=0) { | |
tmpStr=point[TailArrow] + " "; | |
tmpStr2=computePos(substr(point[TailArrow],2), tmpN.pos, arrowSize); | |
} else { | |
tmpStr2=point[firstPt]; | |
} | |
////// do not use firstPt/lastPt as the other end of the arrowhead/arrowtail | |
tmpStr=tmpStr + tmpStr2 + " " + tmpStr2 + " " + tmpN.pos + " " + tmpN.pos; | |
newE=addEdge(workE.tail, tmpN, workE, tmpStr, DirTail[dirStr], "", ">^leave^<", ""); | |
if (show) | |
newE.color=showColor; | |
////// edge #2 (reduced version of workE) | |
newE=addEdge(tmpN, workE.head, workE, "", DirHead[dirStr], "", "", ">^leave^<"); | |
if (saveTailLabel!="") | |
newE.taillabel=saveTailLabel; | |
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=createEdgeNode(workE, "H", headPt[oldE], nextToHead[oldE], hasHeadArrow[oldE], tailPt[oldE], workE.oldHeadport); | |
if (HeadArrow>=0) { | |
tmpStr=point[HeadArrow] + " "; | |
tmpStr2=computePos(substr(point[HeadArrow],2), tmpN.pos, arrowSize); | |
} else { | |
tmpStr2=point[lastPt]; | |
} | |
////// do not use firstPt/lastPt as the other end of the arrowhead/arrowtail | |
tmpStr=tmpStr + tmpStr2 + " " + tmpStr2 + " " + tmpN.pos + " " + tmpN.pos; | |
newE=addEdge(tmpN, workE.head, workE, tmpStr, DirHead[dirStr], "", "", ">^leave^<"); | |
newE.taillabel=""; | |
if (show) | |
newE.color=showColor; | |
////// edge #2 (reduced version of workE) | |
newE=addEdge(workE.tail, tmpN, workE, "", DirTail[dirStr], "", ">^leave^<", ""); | |
if (saveHeadLabel!="") | |
newE.headlabel=saveHeadLabel; | |
deleteIt[workE]=1; | |
print("////////////////////// end of edge ", $.name, " ///////////////////////////////////"); | |
} | |
} | |
END_G{ | |
print("////////////////////////// END_G"); | |
if (debug==0) { | |
for (deleteIt[oldE]) { | |
delete(Root, oldE); | |
} | |
} | |
$G.splines="ortho"; | |
} |
Lately I've been having a problem with encoding after typing
dot -Gsplines=true myFile.gv > myFile.dot
gvpr -cf fixOrtho.gvpr myFile.dot >myfileFixed.dot
As a result after entering
neato -n -Tpng myFileFixed.dot >myfileFixed.png
png does not open even if UTF-8 encoding without BOM is applied to each file in the process
(I tried to enter the above commands both in the PyCharm terminal and in the cmd, I also tried changing the encoding both through PyCharm and through Notepad++)
although I haven't encountered this before
Does using the -o option on the neato command line fix the problem?
neato -n -Tpng myFileFixed.dot -o myfileFixed.png
Now the image opens, but the graphs look broken... maybe it’s a problem with the encoding. If don’t convert the files to UTF-8 without BOM, an error appears
Error: myFile.dot: syntax error in line 1 near '��g'
This problem appeared relatively recently and did not exist before.
with conversion to UTF-8 after each step and using the command with the -o option (as per your recommendation), a warning appears
Warning: the bounding boxes of some nodes touch - falling back to straight line edges
And as mentioned above, the graphs look "broken" in the picture but myFileFixed.dot itself looks not bad in Graphviz Online.
Try using the -o option on the dot and gvpr commands also.
dot -Gsplines=true myFile.gv -o myFile.dot
gvpr -cf fixOrtho.gvpr myFile.dot -o myfileFixed.dot
neato -n -Tpng myFileFixed.dot -o myfileFixed.png
p.s. the fixOrtho.gvpr program was not sufficiently tested & has known bugs. I have a partial improvement and will post it, in the next few days, but it will still not be a great solution.
Thanks, everything works now. Looking forward to updating this fix. Will an implementation be added so that the ends of the graphs, if they arrive at the same port, will be located at the same point(if the nodes have corresponding head and tail)?
I hope the just installed (6/10/24) (m/d/y) version is better.
Improved, but still needs clean-up & more testing.
Fixed arrowheads that were not orthogonal. Or hope this does.
carry over any taillabel and/or headlabel settings
NOTE: use neato -n2 (not neato -n)
fix(?) taillabel bug
Code clean-up & bug fixed (prevent duplicate edges)
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)
