Created
July 21, 2023 01:50
-
-
Save steveroush/fd660db8b77816fc14628a6eb7cec676 to your computer and use it in GitHub Desktop.
a radial sociogram shell archive - 3 sociogram creators
This file contains 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
#!/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