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?

2 answers

  • answered 2018-11-08 06:47 Gene

    If it's really just a minor edit of the input files joined, then perl is a natural fit:

    use strict;
    sub main {
      local $/ = undef;
      print "digraph {\n";
      for my $f (@ARGV) {
        open(F, $f) or die $!;
        my $text = <F>;
        close(F);
        $text =~ s/digraph/subgraph/;
        $text =~s/^/  /mg;
        print $text;
      }
      print "}\n";
    }
    
    main;
    

    Then

    $ perl merge.pl 1.dot 2.dot 3.dot
    digraph {
      subgraph {
        A -> B
        A -> C
      }
      subgraph {
        D -> E
        E -> F
      }
      subgraph {
        D -> G
        G -> A
      }
    }
    

  • answered 2018-11-08 08:50 vaettchen

    For exactly the situation you are describing, using the sample files you provide, there is a very simple answer using m4 - a standard GNU Linux tool that should be installed by default in most distributions.

    Create a file merge123.m4 with this content:

    digraph 123 {
    define(`digraph',`subgraph')
    include(1.dot)
    include(2.dot)
    include(3.dot)
    }
    

    and execute it with the command

    m4 merge123.m4 > 123.dot
    

    and the resulting 123.dot file will be

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

    If you don't like the empty lines, close each line in the script with dnl (I believe this stands for "delete new line"), for example

    include(1.dot)dnl
    

    m4 is extremely useful as it adds features to graphviz that are really helpful for more involved projects; see also this SO question.