Skip to content

Instantly share code, notes, and snippets.

@steveroush
Created July 21, 2023 01:50
Show Gist options
  • Save steveroush/fd660db8b77816fc14628a6eb7cec676 to your computer and use it in GitHub Desktop.
Save steveroush/fd660db8b77816fc14628a6eb7cec676 to your computer and use it in GitHub Desktop.
a radial sociogram shell archive - 3 sociogram creators
#!/bin/sh
cat <<QUitquIT
# This is a shell archive.
#
# to execute this file, type: sh socioGram.d_archive.sh
#
# On Linux/UNIX/MacOS(?) systems it is an executable program that will create
# a subdirectory (named socioGram.d) in the currect directory and then
# install multiple (text) files in that directory.
# This shell archive and all contents are humanly readable - no binary files.
#
# Windows OS users will have to manually cut this file up and create any files
# that are desired. Sorry.
#
# To proceed, type "y" at the prompt.
#
QUitquIT
read -p "Type y to proceed " ans
if [ "$ans" != "y" ];then echo "exiting";exit;fi
if [ -d "socioGram.d" ];then
if [ -w "socioGram.d" ];then echo "directory socioGram.d exists and is writable";
else echo "directory socioGram.d exists but is NOT writable\nExiting"; exit; fi
else
mkdir "socioGram.d" ;
if [ -d "socioGram.d" ];then
if [ -w "socioGram.d" ];then echo "directory socioGram.d has been created and is writable";
else echo "directory socioGram.d has been created but is NOT writable\nExiting"; exit;
fi
fi
fi
echo writing socioGram.d/addRingLabels.gvpr
cat >socioGram.d/addRingLabels.gvpr <<'STopstOP'
/**********************************************************
addRingLabels.gvpr -
a very specialized gvpr program that adds labels to the output of
twopiCircles.gvpr
**********************************************************/
N[name=="____Circle____*"&&ringNo>0]{
$.xlabel=(string)(1 + (int)$.rank);
$.xlp=(string)($.X+(72*($.width/2))) +"," +(string)$.Y;
}
STopstOP
echo writing socioGram.d/dotToRadial.gvpr
cat >socioGram.d/dotToRadial.gvpr <<'STopstOP'
/******************************************************************
dotToRadial - take a dot input & convert to a ranked, radial output
similar to twopi, kind-of
Note, each node must have a "rank" value. Easiest way: dot -Gphase=2
*******************************************************************/
BEGIN{
int i, LR=0, Rank, nodeCnt[], nxtID=0;
int t, radDist, delta;
float x,y,minX,minY,maxX,maxY, rankMinX[], rankMaxX[], rankMinY[], rankMaxY[];
node_t aNode, ID[];
graph_t Root;
string nodesInRank[], filename, centerName, tok[int];
////////////////////////////////////////////////////////////////////////////
// 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);
exit(9);
}
////////////////////////////////////////////////////////////////////////////
// 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.;
}
}
BEG_G{
if (ARGC>0)
filename=ARGV[0];
Root=$G;
print("// file: ", filename);
if (hasAttr($G,"layout") && $G.layout!="dot" && $G.layout!="")
doErrs($.name + " has bad layout attribute (" + $.layout + ")");
if (hasAttr($G,"rankdir") && $G.rankdir=="@(LR|RL)")
LR = 1;
$G.bb="";
centerName="__center__";
// "root" is reserved word, use aget
if (hasAttr($G,"root") && aget($G,"root")!="") {
centerName=aget($G,"root");
}
}
N{
//print("// NODE: ", $.name);
if (!(hasAttr($,"rank") && $.rank!="")) {
doErrs($.name + " is missing the \"rank\" attribute");
}
ID[++nxtID]=$;
/////////////////////////////////////////////////////////////
// well, do we add 1 or not ???????
// add 1 to rank as provided by dot
Rank=(int)($.rank);
nodeCnt[Rank]++;
nodesInRank[Rank]+=(string)nxtID + ":";
minX=$.X-strInch2pt($.width)/2.;
maxX=$.X+strInch2pt($.width)/2.;
minY=$.Y-strInch2pt($.height)/2.;
maxY=$.Y+strInch2pt($.height)/2.;
if (rankMinX[Rank]==0 && rankMaxX[Rank]==0 && rankMinY[Rank]==0 && rankMaxY[Rank]==0) {
rankMinX[Rank]=minX;
rankMaxX[Rank]=maxX;
rankMinY[Rank]=minY;
rankMaxY[Rank]=maxY;
} else {
if (rankMinX[Rank]>minX) rankMinX[Rank]=minX;
if (rankMaxX[Rank]<maxX) rankMaxX[Rank]=maxX;
if (rankMinY[Rank]>minY) rankMinY[Rank]=minY;
if (rankMaxY[Rank]<maxY) rankMaxY[Rank]=maxY;
}
//print("// NODE: ", $.name," ", Rank);
}
END_G{
float vector, rankMin[], rankMax[];
int nCnt;
delta=1; // default to 1 inch, to start
radDist=0;
if (LR) {
//print("// LEFT RIGHT");
for (rankMinX[Rank]) {
rankMin[Rank]=rankMinX[Rank];
rankMax[Rank]=rankMaxX[Rank];
}
} else {
//print("// TOP BOTTOM");
for (rankMinY[Rank]) {
rankMin[Rank]=rankMinY[Rank];
rankMax[Rank]=rankMaxY[Rank];
}
}
// for each rank
for (rankMin[Rank]) {
//print("// rank: ", Rank, " min: ", rankMin[Rank]," max: ",rankMax[Rank]);
// start of nextrank
radDist=Rank * delta;
unset(tok);
// compare nCnt & nodeCnt[Rank] ????? (why both?)
nCnt=tokens(nodesInRank[Rank], tok, ":");
if (Rank==0){
aNode=node(Root, centerName);
aNode.shape="point";
aNode.width=.15;
aNode.style="filled";
aNode.fillcolor="pink";
aNode.radialdistance=radDist;
aNode.radialangle=0;
}
for (tok[t]) {
aNode=ID[(int)tok[t]];
if (LR)
vector=aNode.X;
else
vector=aNode.Y;
aNode.radialdistance=radDist;
aNode.radialangle=(t+1)*(345./nodeCnt[Rank]);
//aNode.radialangle=345*(aNode.X-rankMinX[Rank])/(rankMaxX[Rank]-rankMinX[Rank]);
//aNode.radialangle=345*(vector-rankMin[Rank])/(rankMax[Rank]-rankMin[Rank]);
aNode.pos="";
}
}
}
STopstOP
echo writing socioGram.d/radialLayout.gvpr
cat >socioGram.d/radialLayout.gvpr <<'STopstOP'
// NOTE: this program uses the "+" operator to concatenate strings. This is undocumented!!
/********************************************************************************
RadialLayout - a radial layout engine for Graphviz
radialLayout.gvpr takes a super-subset of Graphviz input and creates an enhanced output with explicit positioning for all nodes. Positioning for each node is based on new angle & distance attributes. This output can be fed into `neato -n` (see https://graphviz.org/faq/#FaqDotWithNodeCoords) to route all defined edges.
New node attributes:
- radialangle - angle from the origin line units: radians, degrees, or "fraction" (3/17) (divide 360 into 17 slices, and position at 3rd). By default, angles are in counter-clockwise direction and are degrees.
- radialdistance distance from 0,0 in direction/angle specified by radialangle units: inches or points (in or pt). No unit defaults to inches. (e.g. 72pt == 1in == 1)
New graph attributes:
- radialclockwise used to request clockwise angle measurements (default is counterclockwise). Values: true or false, false is default
- radialstart set origin line, where default origin line is a line heading from the center of the graph to the right or (a line from 0,0 toward 999999,0) or (a line going due East). Legal values: (clock points) 1oc, 2oc, ..., 12oc and (compass points) n, s, e, w, ne, ne, se, sw
Of note:
All nodes must have both radialangle & radialdistance values
clusters do not (easily) work - a neato issue
there is a gvpr problem setting label="" (no label). Set a node or edge label to "" like this: `{node [label=""] X Y Z}`
gvpr is documented here: https://graphviz.org/pdf/gvpr.1.pdf
neato -n is documented here: https://graphviz.org/pdf/gvpr.1.pdf
Only lightly tested!!
How to use:
`gvpr -c -f radialLayout.gvpr myfile.gv | neato -n -Tpng >myfile.png`
Which Graphviz attributes do not apply?
- attributes that only apply to a subset of the Graphviz engines (e.g. neato only)
- attributes that affect positioning (e.g. rank, nodesep, weight)
- attribute applicability (early guess):
- _background - yes
- Damping - no
- URL - yes
- area - yes
- arrowhead - yes
- arrowsize - yes
- arrowtail - yes
- bgcolor - yes
- center - yes
- charset - yes
- class - yes
- clusterrank - no
- color - yes
- colorscheme - yes
- comment - yes
- compound - yes
- concentrate - yes
- constraint - no
- defaultdist - yes
- decorate - yes
- dim - yes
- dimen - yes
- dir - yes
- diredgeconstraints - yes
- distortion - yes
- dpi - yes
- edgeURL - yes
- edgehref - yes
- edgetarget - yes
- edgetooltip - yes
- epsilon - no
- esep - no
- fillcolor - yes
- fixedsize - yes
- fontcolor - yes
- fontnames - yes
- fontname - yes
- fontpath - yes
- fontsize - yes
- forcelabels - yes
- gradientangle - yes
- group - no
- headURL - yes
- headclip - yes
- headhref - yes
- headlabel - yes
- headport - yes
- headtarget - yes
- headtooltip - yes
- height - yes
- href - yes
- id - yes
- image - yes
- imagepath - yes
- imagepos - yes
- imagescale - yes
- inputscale - yes
- K - no
- label - yes
- labelURL - yes
- labelangle - yes
- labeldistance - yes
- labelfloat - yes
- labelfontcolor - yes
- labelfontname - yes
- labelfontsize - yes
- labelhref - yes
- labeljust - yes
- labelloc - yes
- labeltarget - yes
- labeltooltip - yes
- label_scheme - yes
- landscape - yes
- layer - yes
- layers - yes
- layerlistsep - yes
- layerselect - yes
- layersep - yes
- layout - must be neato
- len - no
- levels - no
- levelsgap - no
- lhead - yes
- ltail - yes
- margin - yes
- maxiter - no
- mclimit - no
- mindist - no
- minlen - no
- mode - no
- model - no
- mosek - no
- newrank - no
- nodesep - no
- nojustify - yes
- normalize - no
- notranslate - yes
- ordering - no
- orientation - yes
- outputorder - yes
- overlap - no
- overlap_scaling - no
- overlap_shrink - no
- pack - no
- packmode - no
- pad - no
- pagedir - yes
- pencolor - yes
- penwidth - yes
- peripheries - yes
- pin - no
- quadtree - no
- quantum - no
- rank - no
- rankdir - no
- ranksep - no
- regular - yes
- remincross - no
- repulsiveforce - no
- resolution - yes
- root - no
- rotate - yes
- rotation - no
- samehead - yes
- sametail - yes
- samplepoints - yes
- searchsize - no
- sep - yes
- shape - yes
- shapefile - yes
- showboxes - yes
- sides - yes
- skew - yes
- smoothing - yes
- sortv - no
- start - no
- style - yes
- stylesheet - yes
- tailURL - yes
- tailclip - yes
- tailhref - yes
- taillabel - yes
- tailport - yes
- tailtarget - yes
- tailtooltip - yes
- target - yes
- tooltip - yes
- truecolor - yes
- xdotversion - yes
- viewport - yes
- voro_margin - no
- weight - yno
- width - yes
- xlabel - yes
- z - yes
********************************************************************/
BEGIN{
int clockwise, noLabel[];
float x,y, start;
float theta, D, pi=3.14159265;
float deg2rad=3.14159/180.;
string numstr="?([+-])@(+([0-9])?(.*([0-9]))|\.+([0-9]))";
string wrkStr, thetaS, errStr;
node_t aNode;
graph_t theRoot;
////////////////////////////////////////////////////////////////////////////
// do NOT call with direct output from sprintf
// - there is a bug - string will be empty
void doErrs(string eString){
printf(2, "(%s) Error: %s\n", $F, eString);
}
/////////////////////////////////////////////////////////////////////////
// NOTE: returns string "" if invalid input (yuck)
/////////////////////////////////////////////////////////////////////////
string computeAngle(string Astr){
int Alen, Aerr;
float Afloat;
string Awrk, Atok[int], ASrslt;
Aerr=0;
Awrk=Astr;
// Alen=length(Astr)-1;
switch(Astr){
case "n":
case "12oc":
Afloat=90*deg2rad;
break;
case "1oc":
Afloat=60*deg2rad;
break;
case "ne":
Afloat=45*deg2rad;
break;
case "2oc":
Afloat=30*deg2rad;
break;
case "e":
case "3oc":
Afloat=0;
break;
case "4oc":
Afloat=330*deg2rad;
break;
case "se":
Afloat=-45*deg2rad;
break;
case "5oc":
Afloat=300*deg2rad;
break;
case "s":
case "6oc":
Afloat=270*deg2rad;
break;
case "7oc":
Afloat=240*deg2rad;
break;
case "sw":
Afloat=-105*deg2rad;
break;
case "8oc":
Afloat=210*deg2rad;
break;
case "w":
case "9oc":
Afloat=180*deg2rad;
break;
case "10oc":
Afloat=150*deg2rad;
break;
case "nw":
Afloat=-45*deg2rad;
break;
case "11oc":
Afloat=120*deg2rad;
break;
default:
if (Astr=="*(?)@(r|rad|rads|radians)"){
//print("// RADIANs: ", Astr);
Alen=index(Astr,"r");
//print("// Alen: ", Alen);
Awrk=substr(Astr,0,Alen);
//print("// radians work: ", Awrk);
if (Awrk==numstr)
Afloat=(float)Awrk;
else{
Aerr=1;
}
}else if (Astr=="*(?)@(d|deg|degrees)"){
//print("// DEGREES: ", Astr);
Alen=index(Astr,"d");
Awrk=substr(Astr,0,Alen);
if (Awrk==numstr)
Afloat=((float)Awrk)*deg2rad;
else{
Aerr=1;
}
}else if (Astr==numstr + "([/])" + numstr){
//print("// Nth: ", Astr);
split(Astr,Atok,"/");
if ((int)Atok[1]<=0){
Aerr=1;
Afloat=0;
print("// error bad denominator: ", Astr);
}else{
Afloat=2*pi*((float)Atok[0]/(float)Atok[1]);
}
}else if (Astr==numstr){
//print("// default (degrees): ", Astr);
Afloat=(float)Astr*deg2rad;
}else{
Aerr=1;
break;
}
}
if (Aerr==1){
print ("// error !!");
ASrslt="";
}else{
ASrslt=(string)Afloat;
}
//print("// returning: >", Afloat, "<");
return ASrslt;
}
////////////////////////////////////////////////////////////////////////
float computeStartAngle(string Astr){
string a1;
a1=computeAngle(Astr);
if (a1==""){
errStr=sprintf("Invalid value for \"radialstart\" %s", theRoot.radialstart);
doErrs(errStr);
}else
return (float)a1;
}
/////////////////////////////////////////////////////////////////////////
float computeNodeAngle(string Astr, node_t Anode){
string a2;
a2=computeAngle(Astr);
if (a2==""){
errStr=sprintf("Node \"%s\" has bad radialangle attribute: %s", Anode.name, Astr);
doErrs(errStr);
// doErrs("Node \"" + Anode.name + "\" has bad radialangle attribute: "+Astr);
}else
return (float)a2;
}
////////////////////////////////////////////////////////////////////////////
// return points
float computeDistance(string Dstr, node_t Dnode){
int Dlen;
float Drslt;
string Dwrk, Dtok[int];
//print("// RAW DISTANCE: ", Dstr);
Dwrk=Dstr;
Dlen=length(Dstr)-1;
if (Dstr=="*(?)@(in|inch|inches)"){
//print("// INCHES ", Dstr);
Dlen=index(Dstr,"i");
Dwrk=substr(Dstr,0,Dlen);
//print("// work: ", Dwrk);
if (Dwrk==numstr)
Drslt=72.*(float)Dwrk; // return points
else{
errStr=sprintf("Node \"%s\" has bad radialdistance attribute: %s", Dnode.name, Dstr);
doErrs(errStr);
// doErrs("Node \"" + Dnode.name + "\" has bad radialdistance attribute: "+Dstr);
Drslt=0;
}
}else if (Dstr=="*(?)@(pt|point|points)"){
//print("// POINTS: ", Dstr);
Dlen=index(Dstr,"p");
Dwrk=substr(Dstr,0,Dlen);
if (Dwrk==numstr)
Drslt=((float)Dwrk);
else{
errStr=sprintf("Node \"%s\" has bad radialdistance attribute: %s", Dnode.name, Dstr);
doErrs(errStr);
//doErrs("Node \"" + Dnode.name + "\" has bad radialdistance attribute: "+Dstr);
Drslt=0;
}
}else if (Dstr==numstr){
//print("// default (inches): ", Dstr);
Drslt=72.*(float)Dstr;
}else {
errStr=sprintf("Node \"%s\" has bad radialdistance attribute: %s", Dnode.name, Dstr);
doErrs(errStr);
//doErrs("Node \"" + Dnode.name + "\" has bad radialdistance attribute: "+Dstr);
Drslt=0;
}
//print("// DISTANCE: returning: ", Drslt);
return Drslt;
}
} // end BEGIN
////////////////////////////////////////////////////////////////////////////
BEG_G{
theRoot=$G;
$G.bb=""; // maybe we should compute new value
clockwise=0;
// first, determine direction /////////////////////////////////////////
if (hasAttr($, "radialclockwise"))
if ($.radialclockwise=="@(1|yes|true)")
clockwise=1;
else if ($.radialclockwise=="@(0|no|false)")
clockwise=0;
else{
errStr=sprintf("Invalid value for \"radialclockwise\" %s", $.radialclockwise);
doErrs(errStr);
//doErrs("Invalid value for \"radialclockwise\" " + $.radialclockwise);
}
// next, determine starting angle /////////////////////////////////////////
start=0;
if (hasAttr($, "radialstart")){
wrkStr=$.radialstart;
start=computeStartAngle(wrkStr);
}
}
////////////////////////////////////////////////////////////////////////////////
N{
//print("// NODE: ", $.name);
if (hasAttr($, "label") && $.label==""){
print("// EMPTY label: ", $.name);
noLabel[$]=1;
}
if (isAttr($G, "N", "label") && $.label==""){
print("// isAttr - EMPTY label: ", $.name);
noLabel[$]=1;
}
$.pin="true"; // needed??? (maybe later processing??)
if (hasAttr($, "radialangle") && $.radialangle!=""){
theta=computeNodeAngle($.radialangle, $);
} else {
errStr=sprintf("Node \"%s\" is missing radialangle attribute", $.name);
doErrs(errStr);
//doErrs("Node \"" + $.name + "\" is missing radialangle attribute" );
}
if (hasAttr($, "radialdistance") && $.radialdistance!=""){
D=computeDistance($.radialdistance, $);
} else {
errStr=sprintf("Node \"%s\" is missing radialdistance attribute", $.name);
doErrs(errStr);
//doErrs("Node \"" + $.name + "\" is missing radialdistance attribute");
}
if (clockwise==1)
theta=-theta; // clockwise
x=0+(D*cos(theta+start));
y=0+(D*sin(theta+start));
$.pos=(string)x + "," + (string)y;
//$.label=$.angle + " + " + $.distance;
}
////////////////////////////////////////////////////////////////////////////
STopstOP
echo writing socioGram.d/rankTwopiForDot.gvpr
cat >socioGram.d/rankTwopiForDot.gvpr <<'STopstOP'
/*************************************************************************
rankTwopiForDot.gvpr -
Though the twopi does not use the term rank (except for the ranksep attribute),
it essentially does (radial) ranking from the root node.
However, while *dot* ranking is (by default) based on longest distance,
twopi ranking is based on soortest (spanning tree) distance.
setTwopiRanks accomplishes the goal in two ways:
- nodes that would have a distance greater than the desired rank will cause invisible nodes and edges added to the graph to "pull" the node in to the desired rank.
- nodes that would have a distance shorter than the desired rank will cause the short edges to be ignored in the rank calculation by appling weight=0 to those edges (see https://graphviz.org/docs/attrs/weight/)
New Attributes, only used by setTwopiRank.gvpr:
- rank - set desired rank for each node
Ranks counted from the center, Root is not counted as a rank
USAGE:
gvpr -cf setTwopiRanks.gvpr myfile.gv | twopi -Tpng >myfile.png
Notes:
- stand-alone nodes are correctly ranked, yea!
- non-outward (to lower or same rank) edges result in incorrect node placement
-
*********************************************************************/
BEGIN{
graph_t Root, subgCirc, subgD;
node_t aNode, center, ID[int], dummyList[];
string Rank[int], centerName;
int i, rnk, nxtID=0, DEBUG=0, reverse=0, skip[];
// 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);
}
void addEdge(node_t fromNode,node_t toNode) {
edge_t iEdge;
if(fromNode==NULL) {
print("// bad tail");
return;
}
if(toNode==NULL) {
print("// bad head");
return;
}
iEdge=edge(fromNode, toNode, "");
if (DEBUG==0) {
iEdge.style="invis"; // debug
}
//iEdge.color="red";
//iEdge.weight="0";
}
}
BEG_G{
node_t TST;
Root=$G;
if (hasAttr($G, "debug") && $G.debug !="") {
DEBUG=1;
}
for (i=0; i<ARGC; i++) {
print("// ARG: ", ARGV[i]);
if (tolower(ARGV[i])=="debug")
DEBUG=1;
if (tolower(ARGV[i])=="twopireversedownward|reverse")
reverse=1;
}
// aget - "root" is reserved word
centerName=aget(Root,"root");
if (centerName=="") {
centerName="__ROOT__";
}
TST=isNode($G, centerName);
center = node($G, centerName);
if (TST==NULL) {
center.shape="point";
if (DEBUG==0)
center.style="invis";
center.label="";
if (DEBUG!=0){
center.style="filled";
center.fillcolor="pink";
}
}
center.rank=0; // trying to mark the origin / 0,0 / center
center.RANKED=1;
$G.newrank="true"; // used by dot, ignored by twopi
}
N{
if ($==center){
if (!(hasAttr($, "rank") && $.rank!="")) {
$.rank=0;
}
}
rnk=(int)$.rank;
ID[++nxtID]=$;
// $.id=nxtID; // yuck
Rank[rnk]+=(string)nxtID + "|";
//print("// node: ", $.name, " rnk: ", rnk, " string: ",Rank[rnk]);
}
END_G{
int cnt, lastCnt, t;
string tok[int];
for(rnk=0; rnk<(# Rank); rnk++) {
print("// checking rank: ", rnk, " ", Rank[rnk]);
// create the subgraphs
subgD=subg($G, "__WRAPPER__");
subgD.peripheries=0;
subgCirc=subg($G, "__CIRCLES__"+(string)rnk);
//setDflt(subgD, "N", "label", "");
if (rnk==0)
subgCirc.rank="source";
else if (rnk==(# Rank)-1)
subgCirc.rank="sink";
else
subgCirc.rank="same";
// create dummy nodes if rank is empty and edges for ranking
if (rnk==0)
aNode=center;
else
aNode = node(subgD, "__dummyNodeAtRank_" +(string)rnk);
if (DEBUG==0)
aNode.style="invis";
aNode.label="";
aNode.shape="point";
aNode.rank=rnk;
aNode.group="__xxxx__";
dummyList[rnk]=aNode;
subnode(subgCirc, aNode);
print("// dummy added: ", aNode.name);
addEdge(dummyList[rnk-1], aNode);
// place node within subgraph for ranking
cnt=tokens(Rank[rnk], tok, "|");
for (i=0; i<cnt; i++) {
aNode=ID[(int)tok[(int)i]];
print("// adding ", aNode.name," to ", subgCirc.name);
subnode(subgCirc, aNode);
}
}
}
STopstOP
echo writing socioGram.d/setTwopiRanks.gvpr
cat >socioGram.d/setTwopiRanks.gvpr <<'STopstOP'
/*************************************************************************
setTwopiRanks: explicitly set ranks for nodes in twopi graphs
Though the twopi does not use the term rank (except for the ranksep attribute),
it essentially does (radial) ranking from the root node.
However, while *dot* ranking is (by default) based on longest distance,
twopi ranking is based on soortest (spanning tree) distance.
setTwopiRanks accomplishes the goal in two ways:
- nodes that would have a distance greater than the desired rank will cause invisible nodes and edges added to the graph to "pull" the node in to the desired rank.
- nodes that would have a distance shorter than the desired rank will cause the short edges to be ignored in the rank calculation by appling weight=0 to those edges (see https://graphviz.org/docs/attrs/weight/)
New Attributes, only used by setTwopiRank.gvpr:
- rank - set desired rank for each node
Ranks counted from the center, Root is not counted as a rank
- twopiReverseDownwardEdges - used to delete edges from higher rank to lower rank
and replace with edges with tail and head swapped.
dir attribute is fixed accordingly
this can also be accomplished by adding '-a reverse' to gvpr commandline
reversing edges changes the layout, though not necessarily for the better
*** note ***
- this attribute can be applied to the (Root) graph, or individually,
to any/all edges
- twopiListReversed - a (Root) graph-level attribute that lists (to stderr)
edges that are reversed
USAGE:
gvpr -cf setTwopiRanks.gvpr myfile.gv | twopi -Tpng >myfile.png
commandline options:
-a reverse - another way to turn on edge reversing, see above
-a debug - makes visible some normally invisible objects
Notes:
- stand-alone nodes are correctly ranked, yea!
- non-outward (to lower or same rank) edges result in incorrect node placement
-
*********************************************************************/
BEGIN{
graph_t Root;
node_t aNode, center, ID[];
edge_t iEdge;
string Rank[], centerName;
int i, nxtID=0, DEBUG=0, glblReverse=0, skip[];
float Dx;
/////////////////////////////////////////////////////////
// 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);
}
/////////////////////////////////////////////////////////
void addInodesandedges(node_t fromNode, node_t toNode, int ranks) {
int r, RNK, skipTo;
node_t n1,n2;
//print("// add: ", fromNode.name, " ", toNode.name," ", ranks);
skipTo=skip[toNode];
n1=fromNode;
for (r=1; r<=ranks; r++) {
RNK=r+(int)fromNode.rank;
if (skipTo>=r) {
//print("// skipping: ", toNode.name," ",RNK);
continue;
}
//print("// not skipping - continue");
if (r<ranks) {
n2 = node(Root, "__"+toNode.name + "_" +(string)RNK);
n2.shape="point";
if (DEBUG==0)
n2.style="invis";
n2.label="";
n2.RANKED=1;
n2.rank=r+(int)fromNode.rank;
//print("// added: ", n2.name);
} else {
n2=toNode;
}
iEdge=isEdge(n1, n2, "");
// no need for duplicates
if (iEdge==NULL)
iEdge=edge(n1, n2, "");
//print("// edge exists: ", iEdge.name);
if (DEBUG==0) {
iEdge.style="invis";
iEdge.color="red";
}
n1=n2;
}
}
}
BEG_G{
node_t TST;
Root=$G;
centerName=aget(Root,"root");
if (centerName=="") {
centerName="__ROOT__";
}
TST=isNode($G, centerName);
center = node($G, centerName);
if (TST==NULL) {
center.shape="point";
center.style="invis";
center.label="";
//center.style="filled";
//center.fillcolor="pink";
}
center.rank=0;
center.RANKED=1;
if (hasAttr($G, "debug") && $G.debug!="") {
DEBUG=1;
}
if (hasAttr($G, "twopiReverseDownwardEdges") && $G.twopiReverseDownwardEdges !="" && istrue($G.twopiReverseDownwardEdges)) {
glblReverse=1;
}
//for (ARGV[i]) { // BUG, why does this produce syntax error??
for (i=0; i<ARGC; i++) {
//print("// ARG: ", ARGV[i]);
if (tolower(ARGV[i])=="debug")
DEBUG=1;
if (tolower(ARGV[i])=="twopireversedownwardedges|reversedownwardedges|reverse")
glblReverse=1;
}
if(glblReverse==1)
print("// GLBLREVERSE is on");
}
N{
if ($==center)
continue;
if (!(hasAttr($, "rank") && $.rank!="")) {
printf(2, "Error: missing rank attribute (%s), exiting\n", $.name);
exit(9);
}
ID[++nxtID]=$;
$.id=nxtID; // yuck
Rank[0+$.rank]+=(string)nxtID + "|";
if ($.indegree==0)
addInodesandedges(center, $, (int)$.rank);
// $.label=$.rank;
}
// add ability to reverse edges if tail.rank > head.rank
E{
int Edelta, lclReverse=0;
edge_t newEdge;
// start w/global reverse value
if(glblReverse==1)
lclReverse=1;
// now override w/ local value if desired
if (hasAttr($, "twopiReverseDownwardEdges") && $.twopiReverseDownwardEdges!="") {
lclReverse=istrue($.twopiReverseDownwardEdges);
if (lclReverse<0 || lclReverse>1) {
printf(2, "Error: edge (%s) has bad value for twopiReverseDownwardEdges (%s) , usin global value (if any)\n", $.name, $.twopiReverseDownwardEdges);
}
}
if (lclReverse==1) {
Edelta=(int)$.head.rank-(int)$.tail.rank;
if (Edelta <0) {
if (hasAttr($G, "twopiListReversed") && $G.twopiListReversed !="" && istrue($G.twopiListReversed)) {
printf(2, "Reversing edge: %s\n", $.name);
print("// Reversing edge: ", $.name);
}
newEdge=edge($.head, $.tail, "");
copyA($, newEdge);
if ((!(hasAttr($, "dir")) || $.dir=="" || $.dir=="forward")) {
newEdge.dir="back";
newEdge.Reversed=1;
} else if ($.dir=="back") {
newEdge.dir="forward";
newEdge.Reversed=1;
} else if ($.dir=="both") {
newEdge.Reversed=1;
}
delete($G, $);
}
}
}
BEG_G{
//reset traverse
}
E{
int delta;
string tmps;
// $.label="D: " + (string)((int)$.head.rank - (int) $.tail.rank);
delta=(int)$.head.rank-(int)$.tail.rank;
// set weight to zero if edge is NOT to a greater rank ???????
if (delta>1 || delta <1) {
// not optimal, will always add invis nodes, even if not needed
//print("// non-incremental edge: ", $.name, " : ", $.tail.rank," : ",$.head.rank);
$.weight=0;
if (delta>1)
addInodesandedges($.tail, $.head, delta);
} else if (delta==1) {
skip[$.head]=(int)$.head.rank; // good through this rank (I hope)
//print("// skip: ", $.tail," ", $.head.name," ",skip[$.head]);
$.head.RANKED=1;
}
}
N{
if (! (hasAttr($, "RANKED") && $.RANKED==1)) {
// take care of stand-alone nodes
addInodesandedges(center, $, (int)$.rank);
$.RANKED=1;
}
}
BEG_G{
int cnt, t, rnk;
string tok[int];
for(rnk=0; rnk<cnt; rnk++) {
//print("// Ordered: ", rnk, " ", Rank[rnk]);
cnt=tokens(Rank[rnk], tok,"|");
for (tok[t]) {
aNode=ID[tok[t]];
//print("// tok: ", tok[t], " ", aNode.name);
addInodesandedges(center, aNode, rnk);
}
}
}
STopstOP
echo writing socioGram.d/socioVersions.sh
cat >socioGram.d/socioVersions.sh <<'STopstOP'
#! /bin/bash
set -x
if [ "$1" = "" ];then
echo "call with 1 parameter: the name of a sociograph input file. like this"
echo "$0 mysocio.gv"
exit 1
fi
f=$1;
F=`basename -s .gv $f`;
T=png
Mlist=""
########################################################
#
# convert and display require ImageMagick package
#
########################################################
##
## functions to finish things
##
setVars (){ ID=$1; O=$F.$ID.$T; Mlist="$Mlist final_$O "; }
finish (){
#set -x
neato -n -Glabel="$F - $ID" -T$T >$O
display -resize 800x800 $O &
# convert and display require ImageMagick package
LBL=`basename -s .$T $O`;
convert $O -pointsize 20 -gravity Center -append -bordercolor black -border 3 -bordercolor white -border 3 final_$O; # label:"$LBL"
}
#
# start with twopi input, modify for dot layout, then make radial
#
setVars twopi2dot
gvpr -cf rankTwopiForDot.gvpr $f |
dot |
gvpr -a"$F" -cf dotToRadial.gvpr |
gvpr -cf radialLayout.gvpr |
neato -GringColors="/greys9/2:/greys9/4:/X11/beige" -n |
gvpr -cf twopiCircles.gvpr |
gvpr -cf addRingLabels.gvpr |
finish
#
# twopi input, fix & edges reversed
#
setVars OrigReversed
gvpr -a reverse -cf setTwopiRanks.gvpr $f |
twopi -GringColors="/greys9/2:/greys9/4:/X11/beige" |
gvpr -cf twopiCircles.gvpr |
gvpr -cf addRingLabels.gvpr |
finish
#
# twopi input, fixed, but no edges reversed
#
setVars Orig
gvpr -cf setTwopiRanks.gvpr $f |
twopi -GringColors="/greys9/2:/greys9/4:/X11/beige" |
gvpr -cf twopiCircles.gvpr |
gvpr -cf addRingLabels.gvpr |
finish
## combine (gvpack-like) into montage
montage $Mlist -title "Versions of $F" -tile 1x7 -geometry +5+5 $F.montage.$T ;
display $F.montage.$T &
STopstOP
echo writing socioGram.d/sociogramA0.gv
cat >socioGram.d/sociogramA0.gv <<'STopstOP'
graph {
normalize = true
a [XXroot = true, shape = circle, style=filled, fillcolor = "lightblue"]
b [XXroot = true, shape = circle, style=filled, fillcolor = "lightblue"]
c [shape = circle, style=filled, fillcolor = "lightblue"]
d [shape = circle, style=filled, fillcolor = "lightblue"]
e [shape = circle, style=filled, fillcolor = "lightblue"]
f [shape = circle, style=filled, fillcolor = "lightblue"]
g [shape = circle, style=filled, fillcolor = "lightblue"]
h [shape = circle, style=filled, fillcolor = "lightblue"]
a -- c
a -- d
a -- f
c -- e
b -- f
b -- g
b -- h
g -- h
}
STopstOP
echo writing socioGram.d/sociogramA1.gv
cat >socioGram.d/sociogramA1.gv <<'STopstOP'
graph {
// removed root attributes
a [ shape = circle, style=filled, fillcolor = "lightblue"]
b [ shape = circle, style=filled, fillcolor = "lightblue"]
c [shape = circle, style=filled, fillcolor = "lightblue"]
d [shape = circle, style=filled, fillcolor = "lightblue"]
e [shape = circle, style=filled, fillcolor = "lightblue"]
f [shape = circle, style=filled, fillcolor = "lightblue"]
g [shape = circle, style=filled, fillcolor = "lightblue"]
h [shape = circle, style=filled, fillcolor = "lightblue"]
// why is it necessary to set weight=0 for these edges (or some of these edges)?
// without, the "ranking" is incorrect
edge [weight=0]
a -- c
a -- d
a -- f
c -- e
b -- f
b -- g
b -- h
g -- h
// add root, left visible
0 [root=true shape=point ] //style=invis]
// add intermediate / invisible nodes
edge [weight=1 style=invis]
node [style=invis shape=point]
0 -- {a b}
0 -- c0 -- c
0 -- d0 -- d
0 -- g0 -- g
0 -- e0 -- e00 -- e
0 -- f0 -- f00 -- f
0 -- h0 -- h00 -- h
}
STopstOP
echo writing socioGram.d/sociogramA2.gv
cat >socioGram.d/sociogramA2.gv <<'STopstOP'
graph {
// removed root references
// set rank attribute for every node (undefined for nodes, but legal)
graph [root="_root"] // arbitrary, but unused name
a [ shape = circle, style=filled, fillcolor = "lightblue" rank=1]
b [ shape = circle, style=filled, fillcolor = "lightblue" rank=1]
c [shape = circle, style=filled, fillcolor = "lightblue" rank=2]
d [shape = circle, style=filled, fillcolor = "lightblue" rank=2]
e [shape = circle, style=filled, fillcolor = "lightblue" rank=3]
f [shape = circle, style=filled, fillcolor = "lightblue" rank=3]
g [shape = circle, style=filled, fillcolor = "lightblue" rank=2]
h [shape = circle, style=filled, fillcolor = "lightblue" rank=3]
a -- c
a -- d
a -- f
c -- e
b -- f
b -- g
b -- h
g -- h
}
STopstOP
echo writing socioGram.d/sociogramB0.gv
cat >socioGram.d/sociogramB0.gv <<'STopstOP'
// sociograph dot
digraph {
graph [root="_root", splines=true twopiListReversed=1]
EA [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
FZ [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 9]
FS [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
GP [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 10]
GL [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
GB [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 4]
LM [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 9]
LR [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 11]
LI [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
MF [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
MTT [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
MT [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
RC [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 1]
YH [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
BB [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
CM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 11]
FZZ [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 9]
GHR [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
GLM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
GM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
KG [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
LAB [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 10]
MG [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
SM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
EA -> FS[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
EA -> MF[color = "black", label = "88% / 100%", fontsize = "10.0", labelfloat = false]
EA -> RC[color = "black", label = "75% / 90%", fontsize = "10.0", labelfloat = false]
EA -> GL[color = "black", label = "63% / 100%", fontsize = "10.0", labelfloat = false]
EA -> GB[dir = both, color = "green", label = "50%", fontsize = "10.0", labelfloat = false]
EA -> MTT[color = "black", label = "38% / 67%", fontsize = "10.0", labelfloat = false]
FZ -> LI[dir = both, color = "green", label = "71%", fontsize = "10.0", labelfloat = false]
FZ -> GB[color = "black", label = "57% / 44%", fontsize = "10.0", labelfloat = false]
FS -> MF[color = "black", label = "83% / 60%", fontsize = "10.0", labelfloat = false]
FS -> RC[dir = both, color = "green", label = "67%", fontsize = "10.0", labelfloat = false]
FS -> GB[color = "black", label = "50% / 33%", fontsize = "10.0", labelfloat = false]
FS -> GL[color = "black", label = "17% / 13%", fontsize = "10.0", labelfloat = false]
GP -> GB[color = "black", label = "78% / 89%", fontsize = "10.0", labelfloat = false]
GL -> GHR[color = "black", label = "75% / 20%", fontsize = "10.0", labelfloat = false]
GL -> RC[color = "black", label = "50% / 70%", fontsize = "10.0", labelfloat = false]
GL -> LI[color = "black", label = "38% / 100%", fontsize = "10.0", labelfloat = false]
GL -> GLM[color = "black", label = "25% / 70%", fontsize = "10.0", labelfloat = false]
GB -> MF[color = "black", label = "100% / 20%", fontsize = "10.0", labelfloat = false]
GB -> LI[dir = both, color = "green", label = "67%", fontsize = "10.0", labelfloat = false]
GB -> RC[color = "black", label = "22% / 20%", fontsize = "10.0", labelfloat = false]
LM -> YH[color = "black", label = "100% / 75%", fontsize = "10.0", labelfloat = false twopiReverseDownwardEdges=1]
LM -> LAB[color = "black", label = "86% / 100%", fontsize = "10.0", labelfloat = false]
MF -> MT[dir = both, color = "green", label = "80%", fontsize = "10.0", labelfloat = false]
MTT -> RC[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
MTT -> MG[color = "black", label = "83% / 50%", fontsize = "10.0", labelfloat = false]
MTT -> GM[color = "black", label = "17% / 43%", fontsize = "10.0", labelfloat = false]
MT -> BB[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
MT -> YH[color = "black", label = "60% / 100%", fontsize = "10.0", labelfloat = false]
MT -> RC[color = "black", label = "40% / 30%", fontsize = "10.0", labelfloat = false]
RC -> MG[color = "black", label = "60% / 83%", fontsize = "10.0", labelfloat = false]
RC -> GM[color = "black", label = "50% / 57%", fontsize = "10.0", labelfloat = false]
RC -> GHR[color = "black", label = "40% / 40%", fontsize = "10.0", labelfloat = false]
RC -> SM[color = "black", label = "10% / 44%", fontsize = "10.0", labelfloat = false]
YH -> KG[color = "black", label = "13% / 33%", fontsize = "10.0", labelfloat = false]
BB -> GLM[color = "black", label = "83% / 100%", fontsize = "10.0", labelfloat = false]
BB -> KG[color = "black", label = "67% / 100%", fontsize = "10.0", labelfloat = false]
BB -> FZZ[color = "black", label = "33% / 75%", fontsize = "10.0", labelfloat = false]
FZZ -> KG[color = "black", label = "100% / 67%", fontsize = "10.0", labelfloat = false]
GHR -> SM[color = "black", label = "100% / 89%", fontsize = "10.0", labelfloat = false]
GHR -> MG[color = "black", label = "80% / 67%", fontsize = "10.0", labelfloat = false]
GLM -> GM[color = "black", label = "30% / 86%", fontsize = "10.0", labelfloat = false]
GM -> SM[color = "black", label = "100% / 56%", fontsize = "10.0", labelfloat = false]
GM -> MG[color = "black", label = "71% / 33%", fontsize = "10.0", labelfloat = false]
MG -> SM[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
}
STopstOP
echo writing socioGram.d/sociogramC0.gv
cat >socioGram.d/sociogramC0.gv <<'STopstOP'
digraph {
graph [root="_root", splines=true]
AL [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 2]
ADB [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
AG [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
AV [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 3]
CG [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
CA [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 4]
GNM [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
LCC [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
LL [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 7]
LC [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
MD [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 3]
MT [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 4]
MDD [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 9]
TC [shape = circle, style = filled, fillcolor = "lightblue", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 10]
AC [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 4]
AP [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 9]
AA [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
CV [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 6]
EH [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 1]
GMG [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
IM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 4]
LR [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 5]
MF [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 3]
RM [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 2]
SC [shape = circle, style = filled, fillcolor = "lightcoral", width = 0.55, fontsize = 12.0, fixedsize = true, rank = 8]
AL -> LL[color = "black", label = "100% / 80%", fontsize = "10.0", labelfloat = false]
AL -> CA[color = "black", label = "90% / 80%", fontsize = "10.0", labelfloat = false]
AL -> MT[dir = both, color = "green", label = "80%", fontsize = "10.0", labelfloat = false]
AL -> MF[color = "black", label = "70% / 25%", fontsize = "10.0", labelfloat = false]
AL -> IM[dir = both, color = "green", label = "60%", fontsize = "10.0", labelfloat = false]
AL -> LCC[color = "black", label = "40% / 60%", fontsize = "10.0", labelfloat = false]
AL -> AG[color = "black", label = "20% / 67%", fontsize = "10.0", labelfloat = false]
AL -> EH[color = "black", label = "10% / 60%", fontsize = "10.0", labelfloat = false]
ADB -> GNM[dir = both, color = "green", label = "67%", fontsize = "10.0", labelfloat = false]
ADB -> LCC[color = "black", label = "33% / 90%", fontsize = "10.0", labelfloat = false]
AG -> MT[color = "black", label = "50% / 50%", fontsize = "10.0", labelfloat = false]
AG -> GMG[color = "black", label = "33% / 86%", fontsize = "10.0", labelfloat = false]
AV -> EH[color = "black", label = "100% / 80%", fontsize = "10.0", labelfloat = false]
AV -> IM[color = "black", label = "90% / 14%", fontsize = "10.0", labelfloat = false]
AV -> MD[color = "black", label = "80% / 86%", fontsize = "10.0", labelfloat = false]
AV -> MDD[color = "black", label = "70% / 90%", fontsize = "10.0", labelfloat = false]
AV -> CA[color = "black", label = "50% / 40%", fontsize = "10.0", labelfloat = false]
AV -> RM[color = "black", label = "40% / 56%", fontsize = "10.0", labelfloat = false]
AV -> MT[color = "black", label = "20% / 70%", fontsize = "10.0", labelfloat = false]
CG -> LR[color = "black", label = "100% / 63%", fontsize = "10.0", labelfloat = false]
CG -> AA[color = "black", label = "89% / 50%", fontsize = "10.0", labelfloat = false]
CG -> LC[dir = both, color = "green", label = "78%", fontsize = "10.0", labelfloat = false]
CG -> EH[color = "black", label = "67% / 20%", fontsize = "10.0", labelfloat = false]
CG -> CV[dir = both, color = "green", label = "44%", fontsize = "10.0", labelfloat = false]
CA -> MT[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
CA -> LL[color = "black", label = "90% / 60%", fontsize = "10.0", labelfloat = false]
CA -> GNM[color = "black", label = "50% / 13%", fontsize = "10.0", labelfloat = false]
CA -> AC[color = "black", label = "30% / 90%", fontsize = "10.0", labelfloat = false]
GNM -> LCC[color = "black", label = "63% / 80%", fontsize = "10.0", labelfloat = false]
GNM -> EH[color = "black", label = "25% / 10%", fontsize = "10.0", labelfloat = false]
LCC -> GMG[color = "black", label = "100% / 43%", fontsize = "10.0", labelfloat = false]
LL -> MT[color = "black", label = "100% / 90%", fontsize = "10.0", labelfloat = false]
LC -> LR[dir = both, color = "green", label = "100%", fontsize = "10.0", labelfloat = false]
LC -> AA[color = "black", label = "86% / 80%", fontsize = "10.0", labelfloat = false]
LC -> CV[color = "black", label = "57% / 60%", fontsize = "10.0", labelfloat = false]
MD -> IM[color = "black", label = "100% / 29%", fontsize = "10.0", labelfloat = false]
MD -> EH[color = "black", label = "71% / 100%", fontsize = "10.0", labelfloat = false]
MD -> RM[color = "black", label = "57% / 78%", fontsize = "10.0", labelfloat = false]
MD -> GMG[color = "black", label = "43% / 57%", fontsize = "10.0", labelfloat = false]
MD -> AC[color = "black", label = "29% / 80%", fontsize = "10.0", labelfloat = false]
MD -> MF[color = "black", label = "14% / 13%", fontsize = "10.0", labelfloat = false]
MT -> AC[color = "black", label = "60% / 70%", fontsize = "10.0", labelfloat = false]
AC -> EH[color = "black", label = "100% / 90%", fontsize = "10.0", labelfloat = false]
AC -> AA[color = "black", label = "50% / 70%", fontsize = "10.0", labelfloat = false]
AC -> RM[color = "black", label = "10% / 33%", fontsize = "10.0", labelfloat = false]
AP -> MF[color = "black", label = "25% / 50%", fontsize = "10.0", labelfloat = false]
AA -> LR[color = "black", label = "100% / 88%", fontsize = "10.0", labelfloat = false]
AA -> CV[dir = both, color = "green", label = "90%", fontsize = "10.0", labelfloat = false]
CV -> LR[color = "black", label = "70% / 75%", fontsize = "10.0", labelfloat = false]
EH -> MF[color = "black", label = "70% / 88%", fontsize = "10.0", labelfloat = false]
EH -> RM[dir = both, color = "green", label = "50%", fontsize = "10.0", labelfloat = false]
EH -> LR[color = "black", label = "30% / 50%", fontsize = "10.0", labelfloat = false]
GMG -> IM[color = "black", label = "100% / 71%", fontsize = "10.0", labelfloat = false]
GMG -> RM[color = "black", label = "71% / 67%", fontsize = "10.0", labelfloat = false]
IM -> MF[color = "black", label = "100% / 38%", fontsize = "10.0", labelfloat = false]
IM -> RM[color = "black", label = "57% / 100%", fontsize = "10.0", labelfloat = false]
MF -> SC[color = "black", label = "75% / 100%", fontsize = "10.0", labelfloat = false]
MF -> RM[color = "black", label = "63% / 89%", fontsize = "10.0", labelfloat = false]
RM -> SC[color = "black", label = "11% / 75%", fontsize = "10.0", labelfloat = false]
}
STopstOP
echo writing socioGram.d/twopiCircles.gvpr
cat >socioGram.d/twopiCircles.gvpr <<'STopstOP'
/*************************************************************************
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
}
STopstOP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment