Graphviz中的图合并

Graphviz中的图合并,graphviz,dot,Graphviz,Dot,我有一组用点语言编码的有向图,我想把它们合并成一个有向图,其中不同输入图中同名的节点合并在一起 例如,给定以下文件: 1.dot: digraph { A -> B A -> C } digraph { D -> E E -> F } digraph { D -> G G -> A } digraph { subgraph { A -> B A -> C } subg

我有一组用点语言编码的有向图,我想把它们合并成一个有向图,其中不同输入图中同名的节点合并在一起

例如,给定以下文件:

1.dot

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

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

digraph {
    A -> B
    A -> C
}
digraph {
    D -> E
    E -> F
}
digraph {
    D -> G
    G -> A
}
digraph {
  subgraph {
    A -> B
    A -> C
  }
  subgraph {
    D -> E
    E -> F
  }
  subgraph {
    D -> G
    G -> A
  }
}
我想获得以下
结果。dot

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

我试图使用
gvpack
,但它重命名了重复的节点

> 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;
        }
}
我发现建议使用
sed
重命名重命名的节点,但这似乎不是很干净


有没有一种方法可以按照我所希望的方式合并图形?

如果它实际上只是对所连接的输入文件进行一次小的编辑,那么perl自然适合:

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;

对于您所描述的具体情况,使用您提供的示例文件,有一个非常简单的答案,即使用标准GNU Linux工具,默认情况下,大多数发行版都应该安装该工具

使用以下内容创建文件
merge123.m4

digraph 123 {
define(`digraph',`subgraph')
include(1.dot)
include(2.dot)
include(3.dot)
}
并用命令执行它

m4 merge123.m4 > 123.dot
并且生成的
123.dot
文件将

digraph 123 {

subgraph {
    A -> B
    A -> C
}

subgraph {
    D -> E
    E -> F
}

subgraph {
    D -> G
    G -> A
}

}
如果不喜欢空行,请使用
dnl
关闭脚本中的每一行(内置的
dnl
表示“放弃到下一行”:

include(1.dot)dnl
m4
非常有用,因为它为
graphviz
添加了一些功能,这些功能对于更多涉及的项目非常有用;另见

编辑以回答评论中的问题:

如果您需要包含文件,但不知道其编号和名称,您(至少)有两个选项:

1) 如果文件的数量非常少,并且您知道它们可能具有的所有名称,那么您可以
sinclude()
all:

digraph 123 {
define(`digraph',`subgraph')
sinclude(1.dot)
sinclude(2.dot)
sinclude(3.dot)
sinclude(4.dot)
sinclude(5.dot)
}
m4
将只包含实际存在的文件,而不会抱怨缺少的文件(
s
表示“无提示”)

2) 如果生成的
.dot
文件数量较大,且名称不可预测,则需要进行一些预处理。创建一个与此类似的shell脚本
include.sh

#!/bin/sh
# get *.dot files (or any pattern you like) into one place
ls *.dot > files.txt
# bring them into a format m4 likes
awk '{print "include(" $1 ")" "dnl"}' files.txt > includes.txt
#done
includes.txt
现在为
m4
提供了必要的信息:

include(1.dot)dnl
include(2.dot)dnl
include(3.dot)dnl
现在修改您的
merge.m4
文件,使其能够利用提供的文件列表(我在此处添加
dnl
,以避免生成的合并文件中出现大量空白):

为了使生成的文件与输入文件分开,最好在合并时使用不同的扩展名:

m4 merge.m4 > merged.gv
现在看起来像

### merge dot files
digraph 123 {
subgraph {
    A -> B
    A -> C
}
subgraph {
    D -> E
    E -> F
}
subgraph {
    D -> G
    G -> A
}
}
我最终使用了一个来执行合并,还有更多

有了这个库,我可以轻松地进入数据结构,根据需要更改节点,并向图中添加属性

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"))
    }

谢谢,这很有趣。如果输入文件的数量及其名称是可变的,是否有办法使用
m4