Search code examples
graphvizdot

Merging graphs in Graphviz


I have a collection of digraphs encoded in DOT language, and i want to merge them into a single digraph where nodes with the same name in different input graphs are merged together.

For example given the following files:

1.dot:

digraph {
    A -> B
    A -> C
}

2.dot:

digraph {
    D -> E
    E -> F
}

3.dot:

digraph {
    D -> G
    G -> A
}

I would like to obtain the following result.dot:

digraph {
  subgraph {
    A -> B
    A -> C
  }
  subgraph {
    D -> E
    E -> F
  }
  subgraph {
    D -> G
    G -> A
  }
}

I tried to use gvpack but it renames duplicate nodes.

> gvpack -u 1.dot 2.dot 3.dot
Warning: node D in graph[2] %15 already defined
Some nodes will be renamed.
digraph root {
        node [label="\N"];
        {
                node [label="\N"];
                A -> B;
                A -> C;
        }
        {
                node [label="\N"];
                D -> E;
                E -> F;
        }
        {
                node [label="\N"];
                D_gv1 -> G;
                G -> A_gv1;
        }
}

I found a similar question on SO that suggest using sed to rename the renamed nodes, but that doesn't seem very clean.

Is there a way to merge the graphs the way i would like them?


Solution

  • I ended up using a Java library to perform the merge, and much more!

    With the library i could easily tap into the data structure, change nodes if need be, and add attributes to the graph.

    A quick example in Kotlin:

    // prepare root graph and set direction
    val wamap = mutGraph("wamap")
        .setDirected(true)
    wamap.graphAttrs().add(RankDir.LEFT_TO_RIGHT)
    
    // add subgraphs from the content of .gv files from disk
    Files.walk(Paths.get("D:\\src\\work\\Wamap"), 1)
        .filter { Files.isRegularFile(it) }
        .filter { it.fileName.toString().endsWith(".gv") }
        .map { Parser.read(it.toFile()) }
        .forEach { it.addTo(wamap) }
    
    // normalize node names to lowercase, to ensure nodes with same name are the same node
    wamap.graphs()
        .flatMap { it.nodes() }
        .forEach { it.setName(it.name().toString().toLowerCase()) }
    
    // output as file, but also render the image directly with all the possible Graphviz layout engines
    File("out/wamap.gv").writeText(wamap.toString())
    Engine.values()
        .forEach { engine ->
            Graphviz.fromGraph(wamap).engine(engine).render(Format.PNG).toFile(File("out/wamap-$engine.png"))
        }