Skip to content

Instantly share code, notes, and snippets.

@steveroush
Last active May 21, 2025 14:27
Show Gist options
  • Save steveroush/eb3743f0cc234339ea1b18f3c3de85f1 to your computer and use it in GitHub Desktop.
Save steveroush/eb3743f0cc234339ea1b18f3c3de85f1 to your computer and use it in GitHub Desktop.
compressCluster.gvpr - replace a Graphviz cluster (and contents) with a single node
/**********************************************************************************
compressCluster.gvpr - replace one or more clusters (and contents) with single node(s)
see help below
**********************************************************************************/
BEGIN{
graph_t aGraph, Root, Parent[], gList[];
node_t aNode, newNode, saveNode[];
edge_t newE;
string cStr, newStr, newName[];
int i, OK, DeleteN[], DeleteG[], cName[];
//////////////// help /////////////////////////////////////////////
string help="
compressCluster.gvpr - replace one or more clusters (and contents) with single node(s)
can be used:
- as a preprocessor, then feed output into dot
or
- as a postprocessor (after dot) to simplify an existing dot graph
Arguments:
-aCcluster_name -aCanotherCluster ...]
Usage examples:
gvpr -aCclusterdogs -aCclustercats -cf compressCluster.gvpr myfile.gv
";
///////////////////////////////////////////////////////////////
graph_t clusterCheck(graph_t thisG) {
for (aGraph = fstsubg(thisG); aGraph; aGraph = nxtsubg(aGraph)) {
if (match(aGraph.name,"cluster")==0 || (hasAttr(aGraph, "cluster") && aGraph.cluster=="true")) {
//print ("// CLUSTER ", aGraph.name);
gList[aGraph.name]=aGraph;
}
aGraph = clusterCheck(aGraph);
}
return thisG;
} // end of clusterTraverse
///////////////////////////////////////////////////////////////
graph_t clusterDelete(graph_t thisG, string newname) {
for (aNode=fstnode(aGraph); aNode; aNode = nxtnode_sg(aGraph, aNode)) {
DeleteN[aNode]=1;
newName[aNode]=newname;
//print ("// old name: ", aNode.name," new name: ", newname);
}
for (aGraph = fstsubg(thisG); aGraph; aGraph = nxtsubg(aGraph)) {
for (aNode=fstnode(aGraph); aNode; aNode = nxtnode_sg(aGraph, aNode)) {
DeleteN[aNode]=1;
newName[aNode]=newname;
}
aGraph = clusterDelete(aGraph, newname);
}
return thisG;
} // end of clusterDelete
//////////////////////////////////////////////////////////////
i=0;
while (i<ARGC) {
if (ARGV[i]=="C") { // -a "C clusterName ..."
cName[ARGV[++i]];
} else if (ARGV[i]=="C*") { // -a "CclusterName ..."
cName[substr(ARGV[i],1)]=1;
} else {
print(help);
exit (0);
}
i++;
}
}
//////////////////////////////////////////////////////////////////////
BEG_G {
Root=$G;
clusterCheck(Root);
OK=1;
for (cName[cStr]) {
aGraph=gList[cStr];
if (aGraph==NULL) {
printf(2, "Error: there is no cluster named %s\n", cStr);
OK=0;
} else {
DeleteG[aGraph]=1;
newStr="_COLLAPSED_" + aGraph.name;
newNode=node(aGraph.parent, newStr);
saveNode[newStr]=newNode;
if (hasAttr(aGraph, "bgcolor")) {
newNode.style="filled";
newNode.fillcolor=aGraph.bgcolor;
} else {
newNode.color="red";
}
if (hasAttr(aGraph, "shape")) {
newNode.shape=aGraph.shape;
} else {
newNode.shape="rect";
}
if (hasAttr(aGraph, "label") && aGraph.label!="" && aGraph.label!="\G") {
newNode.label=aGraph.label;
} else {
newNode.label=aGraph.name;
}
if (hasAttr(aGraph, "bb") && aGraph.bb!="") {
newNode.origBB=aGraph.bb;
float llx, lly, urx, ury;
sscanf (aGraph.bb, "%lf,%lf,%lf,%lf", &llx, &lly, &urx, &ury);
// adjust bb values to remove cluster margin
newNode.pos=(string)(llx+((urx-llx)/2)) + "," + (string)(lly+((ury-lly)/2));
}
clusterDelete(aGraph, newStr);
}
}
if (OK!=1)
exit(1);
}
////////////////////////////////////////////////////////////////////////////////////
E{
//print("// edge: ", $.name);
if (DeleteN[$.tail] == 0 && DeleteN[$.head]==0)
continue; // no cluster involvement, skip
if (newName[$.tail] == newName[$.head]) {
//print("// edge - both ends in same cluster to be deleted: ", $.name);
delete(Root, $);
continue; // all in cluster, just delete
}
//print("// fix this edge: ", $.name);
if (DeleteN[$.tail] == 1) {
newE=edge(saveNode[newName[$.tail]], $.head, "");
copyA($, newE);
newE.label="";
newE.pos="";
//print("// adding edge: ", newE.name);
//print("// deleting edge: ", $.name);
delete(Root, $);
} else {
newE=edge($.tail, saveNode[newName[$.head]], "");
copyA($, newE);
newE.label="";
newE.pos="";
//print("// deleting edge: ", $.name);
delete(Root, $);
}
}
BEG_G{
//reset traverse
}
N[DeleteN[$]==1] {
//print("// deleting node: ", $.name);
delete(Root, $);
}
END_G{
for (DeleteG[aGraph]) {
//print("// deleting graph: ", aGraph.name);
delete(Root, aGraph);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment