Last active
October 22, 2025 19:27
-
-
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 hidden or 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
| /***************************************************** | |
| 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"; | |
| } |
Author
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)?
Author
I hope the just installed (6/10/24) (m/d/y) version is better.
Author
Improved, but still needs clean-up & more testing.
Author
Fixed arrowheads that were not orthogonal. Or hope this does.
Author
carry over any taillabel and/or headlabel settings
NOTE: use neato -n2 (not neato -n)
Author
fix(?) taillabel bug
Author
Code clean-up & bug fixed (prevent duplicate edges)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Try using the -o option on the dot and gvpr commands also.
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.