After many attempts to output either a better laid out single layer or a .dot file with only one layer and no positioning (which I would then reprocess thru dot), I concluded that the layer filtering logic must be implemented after the graph layout engine and likely right before the handoff to the SVG/PS renderers.
Since the layout issue was nagging at me, I eventually dove into GVPR. I’d never used it or anything similar and couldn’t find many examples to help get started but I did find one on another site that I then hacked and cleaned up into a solution for my problem.
I’m posting it here in case it can be useful to others in the future. You would save it into a program file called FindClusterAndNeighborNodes.gvpr and invoke it as:
gvpr -f FindClusterAndNeighborNodes.gvpr -o outputfile.dot -a <name_of_target_cluster> inputfile.dot
This program will:
- look for a subgraph with your parameter name
- find all subgraphs
- copy all nodes in your subgraph and the subgraph itself
- for every edge that starts or ends in your subgraph, copy the other node, the nodes parent subgraph if there is one, and create the edge.
There may be simpler ways to do this in gvpr.
BEG_G
{
// temp variables
graph_t graphTemp;
edge_t edgeTemp;
node_t nodeTemp;
int i;
// variables with purpose
graph_t origCluster; // target cluster in original graph
graph_t targetCluster; // target cluster once created in target graph
graph_t subgraphs[int];
// Initialization
targetCluster = NULL;
origCluster = NULL;
graphTemp = fstsubg($G);
while (graphTemp != NULL)
{
if (graphTemp.name == ARGV[0])
{
// We'll refer to this in the rest of the program
origCluster = graphTemp;
break;
}
graphTemp = nxtsubg(graphTemp);
}
graphTemp = fstsubg($);
i = 0;
while (graphTemp != NULL)
{
if (substr(graphTemp.name, 0, 1) != "%")
{
subgraphs[i++] = graphTemp;
}
graphTemp = nxtsubg(graphTemp);
}
}
N[(origCluster != NULL) && isSubnode(origCluster,$)]
{
if (targetCluster == NULL)
{
// Create a new subgraph for the target cluster
// into the target graph
targetCluster = subg($T, origCluster.name);
copyA(origCluster, targetCluster);
}
// Copy the node to the target graph and in the same target cluster
nodeTemp = node($T, $.name);
copyA($, nodeTemp);
subnode(targetCluster, nodeTemp);
}
E[isSubnode(origCluster, $.tail) != 0]
{
node_t tailNode = node($T, $.tail.name);
// Copy the head node to the target graph and
// in the same target cluster
nodeTemp = node($T, $.head.name);
copyA($.head, nodeTemp);
for (i = 0; i < #subgraphs; ++i)
{
if (isSubnode(subgraphs[i], $.head))
{
graphTemp = subg($T, subgraphs[i].name);
copyA(subgraphs[i], graphTemp);
subnode(graphTemp, nodeTemp);
break;
}
}
edgeTemp = edge_sg($T, tailNode, nodeTemp, $.name);
copyA($, edgeTemp);
}
E[isSubnode(origCluster, $.head) != 0]
{
node_t headNode = node($T, $.head.name);
// Copy the tail node to the target graph and
// in the same target cluster
nodeTemp = node($T, $.tail.name);
copyA($.tail, nodeTemp);
for (i = 0; i < #subgraphs; ++i)
{
if (isSubnode(subgraphs[i], $.tail))
{
graphTemp = subg($T, subgraphs[i].name);
copyA(subgraphs[i], graphTemp);
subnode(graphTemp, nodeTemp);
break;
}
}
edgeTemp = edge_sg($T, nodeTemp, headNode, $.name);
copyA($, edgeTemp);
}