Skip to content

Instantly share code, notes, and snippets.

@steveroush
Last active July 19, 2023 19:59
Show Gist options
  • Save steveroush/aca4cb434042bb67c0eba394c3456d24 to your computer and use it in GitHub Desktop.
Save steveroush/aca4cb434042bb67c0eba394c3456d24 to your computer and use it in GitHub Desktop.
A Graphviz gvpr program to add concentric circles to a twopi graph
/*************************************************************************
twopiCircles: add circles to twopi graph
similar to the "addrings" gvpr program that comes with Graphviz source
New Attributes, only used by twopiCircles.gvpr:
- maxRank - set a maximum number of concentric circles (ranks).
Ranks counted from the center, Root is not counted as a rank
- noOffset - if true, draw circles through the center of nodes
otherwise, draw circles between the ranks
(i.e half-way from one rank to the next)
- ringColors - allows the rings/circles to be colorized
format: 'color1:color2:...'
example: ringColors="/greys9/2:/greys9/4:green:/X11/beige"
(see https://graphviz.org/docs/attrs/colorscheme/)
Command Line Options (to gvpr):
to identify the root node
-a"rootname"
or
-a"root = rootname" (grepped from twopi -v output to stderr)
Optional (preferred) method of identifying the root node is `graph [root=mynode]`, within input file
USAGE:
twopi myfile.gv | gvpr -cf twopiCircles.gvpr >myFileNowWithConcentrigRings.gv
then:
neato -n -Tpng myFileNowWithConcentrigRings.gv >myFileNowWithConcentrigRings.png
*********************************************************************/
BEGIN{
node_t center;
float x[], y[];
int i, j, l, max, rsCnt, maxRankSepPt, maxRSPindx, doOffset, maxRanks;
string rankSep[int];
int RS, rankSepPt[int];
string POS, st, tok[int];
graph_t Root, tempG, tempGx, circleG ;
void dumpG(graph_t aGraph, string txt) {
print("// DUMP ", txt);
write(aGraph);
print("//////////////////////");
}
// bug fixed 6/30/23
int istrue(string checkme) {
int rc;
if (tolower(checkme) == "@(1|yes|true)") // ksh syntax
rc=1;
else
rc=0;
return(rc);
}
int idistance(float x1,float y1,float x2,float y2) {
int d;
d=(int)sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
return d; // points
}
}
BEG_G{
string centerName;
Root=$G;
// fix 6/17/23
if (hasAttr($G, "layout") && $G.layout!="") {
$G.layout="neato";
}
// fix wierd graph label bug??
if (hasAttr($G, "lp") && $G.lp!="")
$G.lp="";
// maxRank added 6/17/23
maxRanks=99999; // cheesy
if (hasAttr($G, "maxRank") && $G.maxRank!="") {
maxRanks=$G.maxRank;
}
// noOffset option added 6/17/23
doOffset=1;
if (hasAttr($G, "noOffset") && $G.noOffset!="" && istrue($G.noOffset)) {
doOffset=0;
}
max=0;
// use aget because "root" is a reserved word
centerName=aget($G,"root");
if (centerName=="") {
switch (ARGC) {
case 1:
case 3:
centerName=ARGV[ARGC-1];
break;
default:
printf(2, "Error: missing \"root\" value on command line, exiting\n");
exit (1);
break;
}
}
// yikes, this code got duped, try to clean up
//print("// CENTER: ", centerName);
center = node($, centerName); // 1st or 3rd argument
if (center==NULL) {
printf(2, "Error: unknown node (%s), exiting\n", centerName);
exit(9);
}
if (hasAttr(center, "pos") && center.pos!="") {
// all is ok
}else{
printf(2, "Error: \"root\" (%s) node has no \"pos\" value, exiting\n", center.name);
exit (1);
}
rankSep[0]=1; // 1 inch
if (hasAttr($G, "ranksep") && $G.ranksep!="") {
rsCnt=tokens($.ranksep, rankSep, ":");
}
// convert array to points (int)
for (rankSep[i]) {
if (rankSep[i] == 0.)
rankSepPt[i]=72;
else
rankSepPt[i]=(int)(72*rankSep[i]);
maxRankSepPt=rankSepPt[i];
maxRSPindx=i;
}
//dumpG(Root,"Root first");
}
N{
if ($==center) {
x[0]=$.X;
y[0]=$.Y;
//print("// center/root: ", $.name, " ", $.pos);
} else {
++max;
x[max]=$.X;
y[max]=$.Y;
}
}
//END_G{
// use beg_g to help place rings at the beginning of the output graph
BEG_G{
int circ=0, dist[], iD, maxD, done, totalD[];
float D;
node_t newN;
string colorScheme="", ringColor[int], gtype;
int ringC=0, colorCnt;
graph_t aGraph, first;
// create subgraph for circles, needed if we colorize
first=fstsubg(Root);
if (first==NULL)
first=Root;
circleG=subg(first, "__COLORED_CIRCLES__");
string Nprefix="____Circle____";
for (i=1; i<=max; i++) {
iD=idistance(x[0], y[0], x[i], y[i]);
dist[iD]++;
}
// find max distance in points
for (dist[iD]) {
if (iD>maxD)
maxD=iD;
}
if (doOffset) {
//print("// offset adjust ");
forr (rankSepPt[i]) { // note forr
rankSepPt[i+1]=rankSepPt[i];
}
rankSepPt[0]=rankSepPt[1]/2;
maxRSPindx++;
}
// add to or remove from rankSepPt, if necessary
totalD[0]=0;
done=0;
totalD[-1]=0; // yuck
for (i=0; i<=maxRSPindx; i++) { // skip 1st?
//print("// i: ", i, " rankSepPt: ",rankSepPt[i], " max: ", maxRSPindx, " done: ",done);
if (done) { // delete these ring entries
unset(rankSepPt,i);
} else {
totalD[i]=totalD[i-1]+rankSepPt[i]; // now have total distance
if ((totalD[i]+5)>maxD || i>=maxRanks) { // delete the rest
done=1;
maxRSPindx=i;
//print("// DONE");
} else if (i==maxRSPindx) {
// this is cheesy, but keep adding entries,
// we will strip unneeded when we are done
rankSepPt[++maxRSPindx]=rankSepPt[i];
//print ("// maxRSPindx: ",maxRSPindx," ",rankSepPt[maxRSPindx]," ",rankSepPt[i]);
}
}
}
unset(totalD,-1); // unyuck
/*****************************************************************************
here is code to colorize the concentric rings
HOWEVER, they are written LAST - on top of all the nodes and edges
Whoops.
To make this work,
- create a new subgraph, at the start of the processing
- add all the rings, largest 1st
********************************************************************/
// added color option 6/22/23
if (hasAttr($G, "ringColors") && $G.ringColors!="") {
colorScheme=$G.ringColors;
colorCnt=tokens(colorScheme,ringColor,":");
}
// from the outside in
forr (totalD[i]) {
//print("// distance count: ", totalD[i]);
circ++;
newN=node(circleG, Nprefix + (string)(circ -1));
newN.shape="circle";
newN.style="dashed";
newN.label="";
// added next two to help external numbering
newN.rank=(circ-1);
newN.ringNo=i;
// fix 6/17/23
newN.penwidth="1";
newN.pos=(string)x[0] + "," + (string)y[0];
newN.width=totalD[i]/36;
if (colorScheme!="") {
newN.style="dashed,filled";
if (ringC>=colorCnt)
ringC=0;
newN.fillcolor=ringColor[ringC++];
}
} // end of forr, rings are created
}
@steveroush
Copy link
Author

code cleanup & minor bug fixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment