Highlight an Edge in Graphviz - graphviz

How might one highlight an edge in graphviz? I'd like to highlight the edge between D and E. One thing I've tried is to put a transparent edge between them on top of the existing edge, but Graphviz does not want to let them overlap the way I would like, instead placing the edges side by side.
I would like a yellow highlight around the edge from D to E.
graph K5 {
graph [splines = false, layout=circo];
edge [penwidth=3];
node [shape=circle];
A;
B;
C;
D;
E;
{
edge [color="red"];
A -- B;
A -- C;
C -- D;
B -- D;
C -- E;
}
{
edge [color="blue"];
node [comment="Wildcard node added automatic in EG."];
D -- E;
A -- E;
B -- C;
B -- E;
A -- D;
}
{
edge [color="#95a616aa" penwidth=5];
node [comment="Wildcard node added automatic in EG."];
E -- D;
}
}

Not sure if this is what you are after, but look at colorlist example here (http://www.graphviz.org/docs/attrs/color/)
graph K5 {
graph [splines = false, layout=circo];
edge [penwidth=3];
node [shape=circle];
A;
B;
C;
D;
E;
{
edge [color="red"];
A -- B;
A -- C;
C -- D;
B -- D;
C -- E;
}
{
edge [color="blue"];
node [comment="Wildcard node added automatic in EG."];
D -- E;
A -- E;
B -- C;
B -- E;
A -- D;
}
{
// see color/colorlist http://www.graphviz.org/docs/attrs/color/
edge [color="lightpink:red:orange:orange:red:lightpink" penwidth=1];
node [comment="Wildcard node added automatic in EG."];
E -- D;
}
}
Giving:

Related

Once I reach the node e, what happens in the method? since on the node e, n, is reference to a null value.Where does program go after return from if

a
/ \
b g
/ \
c d
/
e
\
f
public static Node createData() { //this is the nodes which make up the tree structure
Node a = new Node( "a");
Node b = new Node( "b");
Node c = new Node( "c");
Node d = new Node( "d");
Node e = new Node( "e");
Node f = new Node( "f");
Node g = new Node( "g");
a.left = b; //this block of code connects the nodes to the specific tree should look
a.right = g;
b.left = c;
b.right = d;
c.left = e;
e.right = f;
public static void preOrderTraversal(Node n) { // method recursively traverses through using preOrder
if(n == null)
return;
System.out.println(n.value + " ");
preOrderTraversal(n.left);
preOrderTraversal(n.right);
}
//I understand how a, b, c, and e are being outputted. However, once we reach the node e, n is a reference to a null value. So when we enter the method with a null value, after we enter the if statement, where exactly do we "return" to? Like where does the program sort of jump to/which line is now being looked at at this point? I am struggling to understand how the method works once n is a reference to a null value. By the way, preOrder Traversal has these 3 steps in this order: Visit Node, Traverse left, Traverse right. and then repeats recursively

Breadth First Search (BFS) And Depth First Search (DFS) Code - Need suggestion on how can I improve it further

I have written BFS and DFS code purely with my understanding as below. Which is along with the test example. Looking for the input - how I can make it better in terms of logic & data Structure. I know there are already cooked & may be perfect code in the internet but I wish to attempt mine.
PS: Code is not perfect however I have tested on given example. Apology if you find it messy. Your comments will be welcomed.
package com.company.graph;
import java.util.*;
public class BrethFirstSearch {
public static void main(String...args){
/*
Input Undirected Graph -
2 4 1
A-------B-------C-------D
| \ | \ |
7 | \9 13| 3\ |6
| \ | \ |
E----F----------G-------H
1 8 13
Task 1 - Represent this graph as Data Structure that have optimal Space & Time complexity for store & traversal
Task 2 - Perform "Breadth First Search"
Simplified Representation of Graph where replaced vertex names with numbers..
2 4 1
0-------1-------2-------3
| \ | \ |
7 | \9 13| 3\ |6
| \ | \ |
4----5----------6-------7
1 8 13
*/
// We store number instated of letters since in real world every vertex may have full qualified name ex - "LasVegas" instead of just "A"
Map<Integer,String> vertices = new HashMap<>();
vertices.put(0,"A");
vertices.put(1,"B");
vertices.put(2,"C");
vertices.put(3,"D");
vertices.put(4,"E");
vertices.put(5,"F");
vertices.put(6,"G");
vertices.put(7,"H");
Map<Edge, Integer> edges = new HashMap<>();
//Note - I have store both the side to make Graph search simpler. Comments will be welcomed!!
edges.put(new Edge(0,1), 2);
edges.put(new Edge(0,4), 7);
edges.put(new Edge(0,5), 9);
edges.put(new Edge(1,0), 2);
edges.put(new Edge(1,2), 4);
edges.put(new Edge(2,1), 4);
edges.put(new Edge(2,3), 1);
edges.put(new Edge(2,6), 13);
edges.put(new Edge(2,7), 3);
edges.put(new Edge(3,2), 1);
edges.put(new Edge(3,7), 6);
edges.put(new Edge(4,0), 7);
edges.put(new Edge(4,5), 1);
edges.put(new Edge(5,0), 9);
edges.put(new Edge(5,4), 1);
edges.put(new Edge(5,6), 8);
edges.put(new Edge(6,2), 13);
edges.put(new Edge(6,5), 8);
edges.put(new Edge(6,7), 13);
edges.put(new Edge(7,2), 3);
edges.put(new Edge(7,3), 6);
edges.put(new Edge(7,6), 13);
breadthFirstSearch(vertices, edges);
depthFirstSearch(vertices,edges);
}
static void depthFirstSearch(Map<Integer,String> vertices, Map<Edge, Integer> edges){
System.out.format("%n%n%n%n************ Depth First Search - DFS ***********%n%n");
LinkedList<Integer> listOfVertex = new LinkedList<>(vertices.keySet());
List<Edge> listOfEdges = new ArrayList<>(edges.keySet());
Stack<Integer> dfsStack = new Stack<>();
while (!listOfVertex.isEmpty()){
Integer v = listOfVertex.getFirst();
dfsStack.push(listOfVertex.remove());
System.out.format("*** Start DFS from Vertex %S ***%n", vertices.get(v));
while(!dfsStack.empty()){
Integer vO = v;
for (Edge edge: listOfEdges) {
if (v.equals(edge.getV1()) && listOfVertex.indexOf(edge.getV2()) != -1){ // found new vertex
Integer nextV = edge.getV2();
System.out.format(" Searching from Vertex %S -----> %S%n", vertices.get(edge.getV1()), vertices.get(nextV));
dfsStack.push(nextV);
listOfVertex.remove(nextV);
v = nextV;
break;
}
}
if(vO.equals(v)){ //means not new vertex found from current vertex
v = dfsStack.pop(); //Search for previous vertex
System.out.format("Vertex %S has been conquered %n", vertices.get(v));
}
}
}
}
static void breadthFirstSearch(Map<Integer,String> vertices, Map<Edge, Integer> edges){
System.out.format("************ Breadth First Search - BFS ***********%n%n");
LinkedList<Integer> listOfVertex = new LinkedList<>(vertices.keySet());
List<Edge> listOfEdges = new ArrayList<>(edges.keySet());
BfsQueue bfsQueue = new BfsQueue();
bfsQueue.add(listOfVertex.remove()); //start from 1st vertex = 0 alias A
while (!bfsQueue.isEmpty()){
//remove and start search from this vertex
Integer v = bfsQueue.remove();
System.out.format("Vertex %S has been conquered %n", vertices.get(v));
//Search the Vertices from v
listOfEdges.
forEach(edge -> {
if(v.equals(edge.getV1()) && listOfVertex.indexOf(edge.getV2()) != -1){
bfsQueue.add(edge.getV2());
}});
//Mark the Searched Vertex
Iterator<Integer> i = bfsQueue.getIterator();
while (i.hasNext()){
Integer vertex = i.next();
if (listOfVertex.remove(vertex)){
System.out.format(" Searching from Vertex %S ------> %S %n", vertices.get(v), vertices.get(vertex));
}
}
}
}
static class BfsQueue {
private LinkedList<Integer> list = new LinkedList<>();
Iterator<Integer> getIterator(){
return list.iterator();
}
Integer remove(){
Integer v = null;
if(!list.isEmpty()){
v = list.getFirst();
list.removeFirst();
}
return v;
}
void add(Integer v){
list.add(v);
}
boolean isEmpty(){
return list.isEmpty();
}
boolean isPresent(Integer v){
return list.indexOf(v) != -1;
}
}
static class Edge {
int v1; //1st vertex
int v2; //2nd vertex
public Edge(int v1, int v2) {
this.v1 = v1;
this.v2 = v2;
}
public int getV1() {
return v1;
}
public int getV2() {
return v2;
}
}
}
The Output is :
"C:\Program Files\Java\jdk-11.0.4\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.4\lib\idea_rt.jar=64975:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.4\bin" -Dfile.encoding=UTF-8 -classpath C:\CodeBase\Test\out\production\Test com.company.graph.BrethFirstSearch
************ Breadth First Search - BFS ***********
Vertex A has been conquered
Searching from Vertex A ------> E
Searching from Vertex A ------> B
Searching from Vertex A ------> F
Vertex E has been conquered
Vertex B has been conquered
Searching from Vertex B ------> C
Vertex F has been conquered
Searching from Vertex F ------> G
Vertex C has been conquered
Searching from Vertex C ------> H
Searching from Vertex C ------> D
Vertex G has been conquered
Vertex H has been conquered
Vertex D has been conquered
************ Depth First Search - DFS ***********
*** Start DFS from Vertex A ***
Searching from Vertex A -----> E
Searching from Vertex E -----> F
Searching from Vertex F -----> G
Searching from Vertex G -----> H
Searching from Vertex H -----> C
Searching from Vertex C -----> D
Vertex D has been conquered
Vertex C has been conquered
Searching from Vertex C -----> B
Vertex B has been conquered
Vertex H has been conquered
Vertex G has been conquered
Vertex F has been conquered
Vertex E has been conquered
Vertex A has been conquered
Process finished with exit code 0
One approach I tried is - Implemented edges as a Map where each entry has key as a vertex and value as a List containing edges i.e. all connecting vertices along with the weight. (I inspired through adjacency list approach over the internet and is more efficient than edge list as per my previous approach specially while searching connecting vertices from a
given vertex. The complexity of traversal could be n(n-1) i.e. O(n^2) in Edge List approach assuming all vertices connected to all vertices. Where as (n-1) i.e. O(n) in adjacency list approach as in n vertices graph n can be connected to all n-1 vertices)
i.e. if below is the graph -
2 4 1
0-------1-------2-------3
| \ | \ |
7 | \9 13| 3\ |6
| \ | \ |
4----5----------6-------7
1 8 13
then the map looks like -
Map<Integer, List<Edge>> verticesEdges = new HashMap<>();
verticesEdges.put(0, new LinkedList<>(List.of(new Edge(1,2), new Edge(4,7),new Edge(5,9) )));
verticesEdges.put(1, new LinkedList<>(List.of(new Edge(0,2),new Edge(2,4))) );
verticesEdges.put(2, new LinkedList<>(List.of(new Edge(1,4),new Edge(3,1),new Edge(6,13), new Edge(7,3))));
verticesEdges.put(3,new LinkedList<>(List.of( new Edge(2,1), new Edge(7,6))));
verticesEdges.put(4, new LinkedList<>(List.of(new Edge(0,7),new Edge(5,1) )));
verticesEdges.put(5, new LinkedList<>(List.of(new Edge(0,9), new Edge(4,1),new Edge(6,8) )));
verticesEdges.put(6, new LinkedList<>(List.of(new Edge(2,13), new Edge(5,8), new Edge(7,13))));
verticesEdges.put(7, new LinkedList<>(List.of(new Edge(2,3),new Edge(3,6),new Edge(6,13))));
Now the BFS and DFS code require minor modification. I have presented below just for verification perspective -
package com.company.graph;
import java.util.*;
public class GraphSearch {
public static void main(String...args){
/*
Input Undirected Graph -
2 4 1
A-------B-------C-------D
| \ | \ |
7 | \9 13| 3\ |6
| \ | \ |
E----F----------G-------H
1 8 13
Task 1 - Represent this graph as Data Structure that have optimal Space & Time complexity for store & traversal
Task 2 - Perform "Breadth First Search"
Simplified Representation of Graph where replaced vertex names with numbers..
2 4 1
0-------1-------2-------3
| \ | \ |
7 | \9 13| 3\ |6
| \ | \ |
4----5----------6-------7
1 8 13
*/
// We store number instated of letters since in real world every vertex may have full qualified name ex - "LasVegas" instead of just "A"
Map<Integer,String> vertices = new HashMap<>();
vertices.put(0,"A");
vertices.put(1,"B");
vertices.put(2,"C");
vertices.put(3,"D");
vertices.put(4,"E");
vertices.put(5,"F");
vertices.put(6,"G");
vertices.put(7,"H");
//Implemented edges as a Map where for each entry - key is a vertex and value is List containing edges i.e. all connecting vertices along with the weight !!
Map<Integer, List<Edge>> verticesEdges = new HashMap<>();
verticesEdges.put(0, new LinkedList<>(List.of(new Edge(1,2), new Edge(4,7),new Edge(5,9) )));
verticesEdges.put(1, new LinkedList<>(List.of(new Edge(0,2),new Edge(2,4))) );
verticesEdges.put(2, new LinkedList<>(List.of(new Edge(1,4),new Edge(3,1),new Edge(6,13), new Edge(7,3))));
verticesEdges.put(3,new LinkedList<>(List.of( new Edge(2,1), new Edge(7,6))));
verticesEdges.put(4, new LinkedList<>(List.of(new Edge(0,7),new Edge(5,1) )));
verticesEdges.put(5, new LinkedList<>(List.of(new Edge(0,9), new Edge(4,1),new Edge(6,8) )));
verticesEdges.put(6, new LinkedList<>(List.of(new Edge(2,13), new Edge(5,8), new Edge(7,13))));
verticesEdges.put(7, new LinkedList<>(List.of(new Edge(2,3),new Edge(3,6),new Edge(6,13))));
breadthFirstSearch(vertices, verticesEdges);
//depthFirstSearch(vertices,verticesEdges);
}
static void depthFirstSearch(Map<Integer,String> vertices, Map<Integer, List<Edge>> verticesEdges ){
System.out.format("%n%n%n%n************ Depth First Search - DFS ***********%n%n");
LinkedList<Integer> listOfVertex = new LinkedList<>(vertices.keySet());
Stack<Integer> dfsStack = new Stack<>();
while (!listOfVertex.isEmpty()){
Integer v = listOfVertex.getFirst();
dfsStack.push(listOfVertex.remove());
System.out.format("*** Start DFS from Vertex %S ***%n", vertices.get(v));
while(!dfsStack.empty()){
Integer vO = v;
for (Edge edge: verticesEdges.get(v)) {
if (listOfVertex.indexOf(edge.getV()) != -1){ // found new vertex
Integer nextV = edge.getV();
System.out.format(" Searching from Vertex %S -----> %S%n", vertices.get(v), vertices.get(nextV));
dfsStack.push(nextV);
listOfVertex.remove(nextV);
v = nextV;
break;
}
}
if(vO.equals(v)){ //means not new vertex found from current vertex
v = dfsStack.pop(); //Search for previous vertex
System.out.format("Vertex %S has been conquered %n", vertices.get(v));
}
}
}
}
static void breadthFirstSearch(Map<Integer,String> vertices, Map<Integer, List<Edge>> verticesEdges){
System.out.format("************ Breadth First Search - BFS ***********%n%n");
LinkedList<Integer> listOfVertex = new LinkedList<>(vertices.keySet());
BfsQueue bfsQueue = new BfsQueue();
bfsQueue.add(listOfVertex.remove()); //start from 1st vertex = 0 alias A
while (!bfsQueue.isEmpty()){
//remove and start search from this vertex
Integer v = bfsQueue.remove();
System.out.format("Vertex %S has been conquered %n", vertices.get(v));
//Search the Vertices from v
verticesEdges.get(v).forEach(edge -> {
if(listOfVertex.indexOf(edge.getV()) != -1){
bfsQueue.add(edge.getV());
}});
//Mark the Searched Vertex
Iterator<Integer> i = bfsQueue.getIterator();
while (i.hasNext()){
Integer vertex = i.next();
if (listOfVertex.remove(vertex)){
System.out.format(" Searching from Vertex %S ------> %S %n", vertices.get(v), vertices.get(vertex));
}
}
}
}
static class BfsQueue {
private LinkedList<Integer> list = new LinkedList<>();
Iterator<Integer> getIterator(){
return list.iterator();
}
Integer remove(){
Integer v = null;
if(!list.isEmpty()){
v = list.getFirst();
list.removeFirst();
}
return v;
}
void add(Integer v){
list.add(v);
}
boolean isEmpty(){
return list.isEmpty();
}
boolean isPresent(Integer v){
return list.indexOf(v) != -1;
}
}
static class Edge {
int v; //Connecting Vertex
int w; //weight Of edge
public Edge(int v, int w) {
this.v = v;
this.w = w;
}
public int getV() {
return v;
}
public int getW() {
return w;
}
}
}
When I have ran it -
"C:\Program Files\Java\jdk-11.0.4\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.4\lib\idea_rt.jar=50287:C:\Program Files\JetBrains\IntelliJ IDEA 2018.3.4\bin" -Dfile.encoding=UTF-8 -classpath C:\CodeBase\Test\out\production\Test com.company.graph.GraphSearch
************ Breadth First Search - BFS ***********
Vertex A has been conquered
Searching from Vertex A ------> B
Searching from Vertex A ------> E
Searching from Vertex A ------> F
Vertex B has been conquered
Searching from Vertex B ------> C
Vertex E has been conquered
Vertex F has been conquered
Searching from Vertex F ------> G
Vertex C has been conquered
Searching from Vertex C ------> D
Searching from Vertex C ------> H
Vertex G has been conquered
Vertex D has been conquered
Vertex H has been conquered
************ Depth First Search - DFS ***********
*** Start DFS from Vertex A ***
Searching from Vertex A -----> B
Searching from Vertex B -----> C
Searching from Vertex C -----> D
Searching from Vertex D -----> H
Searching from Vertex H -----> G
Searching from Vertex G -----> F
Searching from Vertex F -----> E
Vertex E has been conquered
Vertex F has been conquered
Vertex G has been conquered
Vertex H has been conquered
Vertex D has been conquered
Vertex C has been conquered
Vertex B has been conquered
Vertex A has been conquered
Process finished with exit code 0

Using clusters reverses the order of rank=same

I have a digraph which I've set to "rankdir=LR;" so that "rank=same" will be top-to-bottom.
I decided to add a few clusters to this graph, but as a result "rank=same" has now become bottom-to-top.
A minimal example shows the problem:
digraph graph {
graph [
rankdir=LR;
nodesep = "0.5 equally",
newrank = true;
];
/* Guide Nodes */
rank1 [style=dotted];
rank2 [style=dotted];
rank1 -> rank2 [style=dotted];
/* Node Clusters */
subgraph cluster1 {
A;
B;
C;
}
/* Node Hierarchy */
A -> Z;
B -> Z;
C -> Z;
/* Node Rank */
{ rank=same;
rank1 -> A -> B -> C [style=dotted];
}
} /* Closes the digraph */
The result that I want is to have from top to bottom: rank1, A, B, C.
The result that I get is from top to bottom: C, B, A, rank1 --- as seen in the picture below.
How can I get the correct order back?
Option 1: Just don't use clusters.
Option 2: Rewrite the "rank=same" line to fit the bottom-to-top direction.
Given the size of my graph, option 2 is simply too much work for too little gain. Is there another option?
EDIT: The answer that marapet gave, does most of what I wanted. However, that solution doesn't work with the following minimal problem:
digraph g {
graph [
rankdir=LR;
nodesep = "0.5 equally",
newrank = true;
];
/* Node Clusters */
subgraph cluster1 {
subgraph cluster2 {
A;
B;
C;
}
P;
subgraph cluster4 {
D;
E;
F;
}
Z;
}
/* Guide Nodes */
rank1 [style=dotted];
rank2 [style=dotted];
/* Guide Nodes Hierarchy */
rank1 -> rank2 [style=dotted];
/* Node Hierarchy */
A -> Z;
B -> Z;
C -> Z;
P -> Z;
D -> Z;
E -> Z;
F -> Z;
/* Rank Constraints */
rank1 -> A -> B -> C -> P -> D -> E -> F [style=dotted, constraint=false];
} /* Closes the digraph */
This results in the following picture:
I can only conclude that the issue I'm having is the result of mixing clusters and non-clusters in the same-rank-edges.
Instead of using rank=same, you may use constraint=false for the same-ranke-edges:
/* Node Rank */
rank1 -> A -> B -> C [style=dotted, constraint=false];
The order of appearance of the nodes (guide nodes and node clusters) should also be changed:
digraph g {
graph [
rankdir=LR;
nodesep = "0.5 equally",
newrank = true;
];
/* Node Clusters */
subgraph cluster1 {
A;
B;
C;
}
/* Guide Nodes */
rank1 [style=dotted];
rank2 [style=dotted];
rank1 -> rank2 [style=dotted];
/* Node Hierarchy */
A -> Z;
B -> Z;
C -> Z;
/* Node Rank */
rank1 -> A -> B -> C [style=dotted, constraint=false];
}
In general, for LR layouts, it helps to lay out the graph top-down and imagine it rotate 90 degrees counterclockwise.
Small extension to edit in the question.
In the edit the lone node 'p' is used, packing it in a subgraph gives a better result, especially when setting the color to white: like:
subgraph cluster3 {
P;
graph[color=white];
}
Only strange thing I get in my output is that there are 2 dotted lines between 'C' and 'P'/
Playing around with the 'dir=back' gave a solution.
Complete code:
digraph g {
graph [
rankdir=LR;
nodesep = "0.5 equally",
newrank = true;
];
/* Node Clusters */
subgraph cluster1 {
subgraph cluster2 {
A;
B;
C;
}
subgraph cluster3 {
P;
graph[color=white];
}
subgraph cluster4 {
D;
E;
F;
}
Z;
}
/* Guide Nodes */
rank1 [style=dotted];
rank2 [style=dotted];
/* Guide Nodes Hierarchy */
rank1 -> rank2 [style=dotted];
/* Node Hierarchy */
A -> Z;
B -> Z;
C -> Z;
P -> Z;
D -> Z;
E -> Z;
F -> Z;
/* Rank Constraints */
rank1 -> A -> B -> C [style=dotted, constraint=false];
D -> E -> F [style=dotted, constraint=false];
P -> C [dir=back, style=dotted, constraint=false];
P -> D [style=dotted, constraint=false];
} /* Closes the digraph */

Can I simultaneously apply styles to many DOT nodes taken from multiple subgraphs?

Consider this DOT sample:
digraph Foo
{
subgraph clusterA
{
A -> B;
}
subgraph clusterB
{
X -> Y;
}
subgraph connection_type_1
{
edge [color=red];
A -> Y;
}
subgraph connection_type_1
{
edge [color=green];
B -> X;
}
subgraph node_type_1
{
node [style=filled, color=".5,.5,.5"]; // THIS LINE DOESN'T WORK
X [label="foo"];
A;
}
}
The structure is set out in the two clusters and the edges are added later in semantically/cosmetically equivalent groups. The edges are coloured as expected.
But this doesn't work with styling nodes. The marked line has no effect unless I move it into one of the cluster* subgraphs, but then it applies to all nodes within that subgraph.
What's odd is that label=foo works in the final subgraph, whereas style doesn't.
I have a feeling that the answer is going to be "you can only set node attributes the first time you mention them", but is there a way to say "the following nodes, wherever they are, should all have the following attributes"?
the line does not work, as it sets default attributes only and the nodes have been created already. the default attributes have no effect. the label is a concrete attribute overriding any default values and therefore takes effect.
So you should reorder the code to
create all nodes (with default attributes)
create all edges (again with default attributes)
assign the nodes to clusters
steps 2 and 3 would create the nodes with the defauls attributes then active. your example did work for the edges only by accident, as you did try it for non existing edges only. it would not have worked for the 2 already defined edges in the clusters.
digraph Foo {
subgraph node_type_1 {
node [style=filled, color=".5,.5,.5"];
A;
X [label="foo"];
}
subgraph node_type_2 {
node [style=none];
B;
Y;
}
subgraph connection_type_1 {
edge [color=red];
A -> Y;
A -> B;
}
subgraph connection_type_2 {
edge [color=green];
B -> X;
X -> Y;
}
subgraph clusterA {
A;
B;
}
subgraph clusterB {
X;
Y;
}
}

How do I place nodes on the same level in DOT?

I want to render several trees simultaneously and place all root nodes and all leaf nodes on the same level.
Here's an example of what I'm trying to do. Root nodes A and X are on the same level, and so are leaf nodes B, D, and Z.
I unsuccessfully tried putting roots in one rank and leaves in another as follows:
digraph G {
rankdir = TB;
subgraph {
A -> B
A -> C
C -> D
X -> Y
rank = same; A; X;
rank = same; B; D; Y;
} /* closing subgraph */
}
And got this outcome where everything is on the same rank.
Any suggestions about what I should be trying? I've already got roots and leaves identified.
Putting the rank = same; ... statements in braces, e.g.:
digraph G {
rankdir = TB;
subgraph {
A -> B
A -> C
C -> D
X -> Y
// note that rank is used in the subgraph
{rank = same; A; X;}
{rank = same; B; D; Y;}
} /* closing subgraph */
}
... gives the desired result:
The ideal structure is actually rank max and rank min. No need for a subgraph or any other shenanigans. GraphViz has explicit facilities for this.
With complex graphs, rank=same will often end up near the middle of the graph. If you mean top and bottom, say top and bottom.
digraph G {
rankdir = TB;
A -> B;
A -> C -> D;
X -> Y;
{ rank=min; A; X; }
{ rank=max; B; D; Y; }
}
Here's a simple example inspired by #William John Holden's comment -
graph {
rankdir=LR;
a -- b -- c;
d -- e -- f;
b -- d; {rank = same; b; d;};
}

Resources