在 Graphviz 中合并图形

Gau*_*ier 5 dot graphviz

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

例如给定以下文件:

1.dot

digraph {
    A -> B
    A -> C
}
Run Code Online (Sandbox Code Playgroud)

2.dot

digraph {
    D -> E
    E -> F
}
Run Code Online (Sandbox Code Playgroud)

3.dot

digraph {
    D -> G
    G -> A
}
Run Code Online (Sandbox Code Playgroud)

我想获得以下内容result.dot

digraph {
  subgraph {
    A -> B
    A -> C
  }
  subgraph {
    D -> E
    E -> F
  }
  subgraph {
    D -> G
    G -> A
  }
}
Run Code Online (Sandbox Code Playgroud)

我尝试使用,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;
        }
}
Run Code Online (Sandbox Code Playgroud)

在 SO 上发现了一个类似的问题,建议使用sed重命名重命名的节点,但这似乎不太干净。

有没有办法按照我想要的方式合并图形?

vae*_*hen 8

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

创建一个merge123.m4包含以下内容的文件:

digraph 123 {
define(`digraph',`subgraph')
include(1.dot)
include(2.dot)
include(3.dot)
}
Run Code Online (Sandbox Code Playgroud)

并使用命令执行它

m4 merge123.m4 > 123.dot
Run Code Online (Sandbox Code Playgroud)

结果123.dot文件将是

digraph 123 {

subgraph {
    A -> B
    A -> C
}

subgraph {
    D -> E
    E -> F
}

subgraph {
    D -> G
    G -> A
}

}
Run Code Online (Sandbox Code Playgroud)

如果您不喜欢空行,请关闭脚本中的每一行dnl(内置dnl代表“丢弃到下一行”:),例如

include(1.dot)dnl
Run Code Online (Sandbox Code Playgroud)

m4非常有用,因为它添加了graphviz对更多涉及的项目真正有用的功能;另请参阅此问题

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

如果您需要包含文件并且不知道它们的编号和名称,您(至少)有两个选择:

1)如果文件数量很少,并且您知道它们可能具有的所有名称,则可以sinclude()全部使用:

digraph 123 {
define(`digraph',`subgraph')
sinclude(1.dot)
sinclude(2.dot)
sinclude(3.dot)
sinclude(4.dot)
sinclude(5.dot)
}
Run Code Online (Sandbox Code Playgroud)

m4只会包含实际存在的文件,不会抱怨丢失的文件(s意思是“静默”)。

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

#!/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
Run Code Online (Sandbox Code Playgroud)

includes.txt现在提供m4必要的信息:

include(1.dot)dnl
include(2.dot)dnl
include(3.dot)dnl
Run Code Online (Sandbox Code Playgroud)

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

### merge dot files
digraph 123 {
define(`digraph',`subgraph')dnl
syscmd(`./include.sh')dnl
include(`includes.txt')dnl
}
Run Code Online (Sandbox Code Playgroud)

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

m4 merge.m4 > merged.gv
Run Code Online (Sandbox Code Playgroud)

现在看起来像

### merge dot files
digraph 123 {
subgraph {
    A -> B
    A -> C
}
subgraph {
    D -> E
    E -> F
}
subgraph {
    D -> G
    G -> A
}
}
Run Code Online (Sandbox Code Playgroud)


Gau*_*ier -1

我最终使用Java 库来执行合并,等等!

通过该库,我可以轻松地利用数据结构,根据需要更改节点,并向图表添加属性。

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"))
    }
Run Code Online (Sandbox Code Playgroud)