Last active
February 20, 2025 08:55
add a grid to any Graphviz graph
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
/************************************************************************** | |
add a grid to a graph in .dot format | |
**************************************************************************/ | |
BEGIN{ | |
int i, Indx, bcnt, LR; | |
string Type, Val, Gcolor[], Gstyle[], Gsize[], tmpstr; | |
string Hcolor, Vcolor, Hstyle, Vstyle, gridType, gridAlign; | |
float Hsize, Vsize, deltaX, deltaY, minX, minY, maxX, maxY; | |
string help="TBD"; | |
float rankPos[]; | |
node_t LL, UR, aNode; | |
edge_t E; | |
graph_t Root; | |
help=" | |
adddGrid.gvpr - add a grid to any Graphviz graph | |
options: | |
grid=horizontal|vertical|both - set direction(s) of grid lines | |
size=rank - grid lines run through center of ranks | |
[GHV]size= - set gridlines to numeric value (123in for inches or 123pt for points), | |
fraction (4 for 1/4th of graph size) or % (10% for 10% of graph size) | |
[GHV]style= - set grid style to valid Graphviz style string | |
[GHV]color= - set grid color to rotating value from provided list | |
note: G|H|V prefix indicates set attribute for horizontal, vertical or both(G) | |
usage examples: | |
dot myFile.gv | gvpr -cf addGrid.gvpr |neato -n2 ... | |
dot myFile.gv | gvpr -a 'size=rank Gcolor=green,purple,red grid=horizontal' -cf addGrid.gvpr |neato -n2 ... | |
"; | |
//////////////////////////////////////////////////////////////////////////// | |
// do NOT call with direct output from sprintf - there is a bug - string will be empty | |
void doErrs(string eString) | |
{ | |
printf(2, "Error:: %s\n", eString); | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
// float inch2pt(float i){ | |
// return 72.*i; | |
// } | |
//////////////////////////////////////////////////////////////////////////// | |
float strInch2pt(string inches) { | |
float f; | |
sscanf(inches, "%lf", &f); | |
//print("// strInch2pt: >", inches, "< ", f, " ", f*72.); | |
return f*72.; | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
string next(string list) { | |
int Cnt, nxt, save[]; | |
string retS, nxtTok[int]; | |
print("// NEXT: ", save[list]); | |
Cnt=tokens(list,nxtTok,",;"); | |
nxt=save[list]; | |
if(nxt>=(Cnt)) { | |
save[list]=0; | |
nxt=0; | |
} | |
retS=nxtTok[nxt]; //save[list]]; | |
save[list]++; | |
return(retS); | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
float gridSizeChk(string sizeStr, float delta) { | |
float size; | |
size=72.; | |
print("// SIZE start: ", sizeStr, " delta: ", delta); | |
if (sizeStr=="(+([0-9])pt)") { // points | |
size=(float)sub(sizeStr,"pt"); | |
} else if (sizeStr=="(+([0-9])in)") { // inches | |
size=72.*(float)sub(sizeStr,"in"); | |
} else if (sizeStr=="(+([0-9])%)") { // percent 20% means 5 intervals | |
size=delta * .01 * (float)sub(sizeStr,"%"); | |
} else if (sizeStr=="(+([0-9]))") { // if bare number, divide by that number | |
size=delta / (float)sizeStr; | |
} else if (sizeStr=="rank") { // align on rank | |
size=-1; // flag? | |
} else { | |
doErrs("bad gridsize attribute value \"" + sizeStr + "\""); | |
} | |
print("// SIZE end: ", sizeStr, " size: ", size, " delta: ", delta); | |
return size; | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
void straightLine(float x1, float y1, float x2, float y2, string C, string S, string lbl) { | |
node_t n1, n2; | |
string tmpStr; | |
tmpstr="__grdNodeA__" + (string) x1 + "_" + (string) y1 +"_" + (string) x2 +"_" + (string) y2; | |
n1=node(Root,tmpstr); | |
if (lbl!="") { | |
n1.label=lbl; | |
n1.shape="plaintext"; | |
} else { | |
n1.label=""; | |
n1.shape="point"; | |
n1.style="invis"; | |
} | |
n1.pos=(string)x1 + "," + (string)y1; | |
tmpstr="__grdNodeB__" + (string) x1 + "_" + (string) y1 +"_" + (string) x2 +"_" + (string) y2; | |
n2=node(Root,tmpstr); | |
n2.label=""; | |
n2.shape="point"; | |
n2.style="invis"; | |
n2.pos=(string)x2 + "," + (string)y2; | |
E=edge(n1,n2,""); // new edge, | |
E.dir="none"; | |
E.color=C; | |
E.style=S; | |
E.penwidth=1.; | |
tmpstr=sprintf("%.2f,%.2f %.2f,%.2f %.2f,%.2f %.2f,%.2f", | |
x1, y1, x1, y1, x2, y2, x2, y2); | |
E.pos=tmpstr; | |
//print("// straight: " + tmpstr); | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
void doGrid() { | |
float n; | |
int II; | |
print("// check grid1 - ", gridType," LR: ",LR," gridAlign: ", gridAlign); | |
II=0; | |
if (gridType=="both" || gridType=="vertical" || (LR==1 && gridAlign=="rank")) { | |
if (LR==1 && gridAlign=="rank") { | |
for (rankPos[n]) { | |
print("// check g1a - ", n); | |
straightLine(n,minY,n,maxY, next(Vcolor), Vstyle, (string)++II); | |
} | |
} else { | |
for (n=minX; n<=maxX; n+=Vsize) { | |
print("// check g1b - ", n," - ",Vsize); | |
straightLine(n,minY,n,maxY, next(Vcolor), Vstyle, (string)++II); | |
} | |
} | |
} | |
print("// check grid2"); | |
II=0; | |
if (gridType=="both" || gridType=="horizontal" || (LR==0 && gridAlign=="rank")) { | |
if (LR==0 && gridAlign=="rank") { | |
forr (rankPos[n]) { | |
straightLine(minX,n,maxX,n, next(Hcolor), Hstyle, (string)++II); | |
print("// n: ",n," ",II); | |
} | |
} else { | |
for (n=maxY; n>=minY; n-=Hsize) { | |
straightLine(minX,n,maxX,n, next(Hcolor), Hstyle, (string)++II); | |
print("// n: ",n," ",II); | |
} | |
} | |
} | |
print("// check g last"); | |
} | |
} | |
///////////////////////////////////////////////////////////////////////////////// | |
BEG_G{ | |
Root=$G; | |
if (hasAttr(Root, "layout")) | |
Root.layout="neato"; | |
Root.phase=""; | |
maxX=(float)xOf(urOf(Root.bb)); | |
minX=(float)xOf(llOf(Root.bb)); | |
deltaX=maxX-minX; | |
maxY=(float)yOf(urOf(Root.bb)); | |
minY=(float)yOf(llOf(Root.bb)); | |
deltaY=maxY-minY; | |
Hcolor="red"; | |
Vcolor="red"; | |
Hstyle="dashed"; | |
Vstyle="dashed"; | |
Hsize=gridSizeChk("72pt", 0.); | |
Vsize=Hsize; | |
gridType="both"; | |
Gcolor["both"]=Val; | |
Gstyle["both"]=Val; | |
Gsize["both"] =Val; | |
i=0; | |
while (i<ARGC) { | |
print("// ARG >", ARGV[i],"<"); | |
Indx=1+index(ARGV[i],"="); | |
Type=substr(ARGV[i],0,1); | |
print("// type: ", Type); | |
Val=substr(ARGV[i],Indx); | |
if (ARGV[i]=="[Gg]rid=*") { | |
gridType=Val; | |
print("// ",ARGV[i], " ",Val," << gridType"); | |
} else if (ARGV[i]=="[GHV]color=*") { | |
if (Type=="H") { | |
Hcolor=Val; | |
} else if (Type=="V") { | |
Vcolor=Val; | |
} else { | |
Hcolor=Val; | |
Vcolor=Val; | |
} | |
print("// ",ARGV[i], " << color"); | |
} else if (ARGV[i]=="[GHV]style=*") { | |
if (Type=="H") { | |
Hstyle=Val; | |
} else if (Type=="V") { | |
Vstyle=Val; | |
} else { | |
Hstyle=Val; | |
Vstyle=Val; | |
} | |
print("// ",ARGV[i], " << style"); | |
} else if (ARGV[i]=="size=rank|Gsize=rank") { | |
gridAlign="rank"; | |
} else if (ARGV[i]=="[GHV]size=*") { | |
if (Type=="H") { | |
Hsize=gridSizeChk(Val, deltaY); | |
} else if (Type=="V") { | |
Vsize=gridSizeChk(Val, deltaX); | |
} else { | |
Hsize=gridSizeChk(Val, deltaY); | |
Vsize=gridSizeChk(Val, deltaX); | |
} | |
print("// ",ARGV[i], " << size"); | |
} else { | |
printf(2, "Bad argument: \"%s\"\n", ARGV[i]); | |
printf(2, "%s\n", help); | |
exit (1); | |
} | |
i++; | |
} | |
//print("// Vsize: ",Vsize); | |
//print("// bb: ",$G.bb, " ", deltaX, " ",deltaY); | |
if (hasAttr(Root, "grid") && Root.grid!="") { | |
tmpstr=tolower(Root.grid); | |
if (tmpstr!="both" && tmpstr!="horizontal" && tmpstr!="vertical") { | |
doErrs("grid attribute value \"" + Root.grid + "\" is illegal (horizontal, vertical, or both)"); | |
exit(8); | |
} | |
gridType=tmpstr; | |
} | |
// set other grid values | |
// - interval (pts or inches) or count | |
// - horiz/vert color | |
// - horiz/vert line style | |
print("// check 1"); | |
if (hasAttr(Root, "gridHcolor") && Root.gridHcolor!="") { | |
Hcolor=Root.gridHcolor; | |
} | |
if (hasAttr(Root, "gridVcolor") && Root.gridVcolor!="") { | |
Vcolor=Root.gridVcolor; | |
} | |
if (hasAttr(Root, "gridcolor") && Root.gridcolor!="") { | |
Hcolor=Root.gridcolor; | |
Vcolor=Root.gridcolor; | |
} | |
if (hasAttr(Root, "gridHstyle") && Root.gridHstyle!="") { | |
Hstyle=Root.gridHstyle; | |
} | |
if (hasAttr(Root, "gridVstyle") && Root.gridVstyle!="") { | |
Vstyle=Root.gridVstyle; | |
} | |
if (hasAttr(Root, "gridstyle") && Root.gridstyle!="") { | |
Hstyle=Root.gridstyle; | |
Vstyle=Root.gridstyle; | |
} | |
if (hasAttr(Root, "gridHsize") && Root.gridHsize!="") { | |
Hsize=gridSizeChk(Root.gridHsize, deltaY); | |
} | |
if (hasAttr(Root, "gridVsize") && Root.gridVsize!="") { | |
Vsize=gridSizeChk(Root.gridVsize, deltaX); | |
} | |
if (hasAttr(Root, "gridsize") && Root.gridsize!="") { | |
Hsize=gridSizeChk(Root.gridsize, deltaY); | |
Vsize=gridSizeChk(Root.gridsize, deltaX); | |
} | |
//print("// check 2 -- Vsize: ",Vsize); | |
LR=0; | |
if (hasAttr(Root,"rankdir")) { | |
if (toupper(Root.rankdir)=="@(LR|RL)") { | |
LR = 1; | |
} else if (toupper(Root.rankdir)=="@(TB|BT)") { | |
LR=0; | |
} | |
} | |
//print("// check 3"); | |
} | |
N{ | |
if (hasAttr($, "pos") && $.pos!="") { | |
if (LR==1) { | |
rankPos[$.X]=1; | |
} else { | |
rankPos[$.Y]=1; | |
} | |
} | |
} | |
END_G{ | |
//print("// check 4"); | |
doGrid(); | |
} |
Try adding outputorder=edgesfirst to your graph (see https://www.graphviz.org/docs/attrs/outputorder/ & https://www.graphviz.org/docs/attr-types/outputMode/).
No guarantees, let me know if it works for you.
It works, thanks a lot!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can we add the grid behind the actual nodes?