在 Graphviz 中合并图形
Merging graphs in Graphviz
我有一组用 DOT 语言编码的二合字母,我想将它们合并成一个单独的二合字母,其中将不同输入图中具有相同名称的节点合并在一起。
例如给定以下文件:
1.dot
:
digraph {
A -> B
A -> C
}
2.dot
:
digraph {
D -> E
E -> F
}
3.dot
:
digraph {
D -> G
G -> A
}
我想获得以下result.dot
:
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;
}
}
我发现 a similar question on SO 建议使用 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;
然后
$ 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
}
}
对于您所描述的情况,使用您提供的示例文件,使用 m4 有一个非常简单的答案 - 一个标准的 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()
它们全部:
digraph 123 {
define(`digraph',`subgraph')
sinclude(1.dot)
sinclude(2.dot)
sinclude(3.dot)
sinclude(4.dot)
sinclude(5.dot)
}
m4
只会包含实际存在的文件,不会抱怨丢失的文件(s
表示 "silent")。
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(" ")" "dnl"}' files.txt > includes.txt
#done
includes.txt
现在向 m4
提供必要的信息:
include(1.dot)dnl
include(2.dot)dnl
include(3.dot)dnl
现在修改您的 merge.m4
文件,使其能够使用提供的文件列表(我在此处添加 dnl
以避免合并结果中出现大量空 space文件):
### merge dot files
digraph 123 {
define(`digraph',`subgraph')dnl
syscmd(`./include.sh')dnl
include(`includes.txt')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
}
}
我最终使用 Java library 来执行合并,还有更多!
有了这个库,我可以轻松地进入数据结构,根据需要更改节点,并向图表添加属性。
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"))
}
我有一组用 DOT 语言编码的二合字母,我想将它们合并成一个单独的二合字母,其中将不同输入图中具有相同名称的节点合并在一起。
例如给定以下文件:
1.dot
:
digraph {
A -> B
A -> C
}
2.dot
:
digraph {
D -> E
E -> F
}
3.dot
:
digraph {
D -> G
G -> A
}
我想获得以下result.dot
:
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;
}
}
我发现 a similar question on SO 建议使用 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;
然后
$ 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
}
}
对于您所描述的情况,使用您提供的示例文件,使用 m4 有一个非常简单的答案 - 一个标准的 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()
它们全部:
digraph 123 {
define(`digraph',`subgraph')
sinclude(1.dot)
sinclude(2.dot)
sinclude(3.dot)
sinclude(4.dot)
sinclude(5.dot)
}
m4
只会包含实际存在的文件,不会抱怨丢失的文件(s
表示 "silent")。
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(" ")" "dnl"}' files.txt > includes.txt
#done
includes.txt
现在向 m4
提供必要的信息:
include(1.dot)dnl
include(2.dot)dnl
include(3.dot)dnl
现在修改您的 merge.m4
文件,使其能够使用提供的文件列表(我在此处添加 dnl
以避免合并结果中出现大量空 space文件):
### merge dot files
digraph 123 {
define(`digraph',`subgraph')dnl
syscmd(`./include.sh')dnl
include(`includes.txt')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
}
}
我最终使用 Java library 来执行合并,还有更多!
有了这个库,我可以轻松地进入数据结构,根据需要更改节点,并向图表添加属性。
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"))
}