Слияние графиков в Графвизе - PullRequest
0 голосов
/ 08 ноября 2018

У меня есть коллекция орграфов, закодированных на языке 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;
        }
}

Я нашел аналогичный вопрос для SO , в котором предлагается использовать sed для переименования переименованных узлов, но это не кажется очень чистым.

Есть ли способ объединить графики так, как мне бы хотелось?

Ответы [ 3 ]

0 голосов
/ 08 ноября 2018

Для конкретной ситуации, которую вы описываете, используя предоставленные вами примеры файлов, есть очень простой ответ, использующий 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 означает «Discard to Next Line» :), например

include(1.dot)dnl

m4 чрезвычайно полезен, поскольку добавляет в graphviz функции, которые действительно полезны для более вовлеченных проектов; см. также этот вопрос SO .

отредактировано, чтобы ответить на вопрос в комментарии:

Если вам нужно включить файлы и вы не знаете их номера и названия, у вас есть (как минимум) два варианта:

1) Если количество файлов довольно мало, и вы знаете все имена, которые они могут иметь, вы можете sinclude() всех их:

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

m4 будет включать только файлы, которые действительно существуют, и не будет жаловаться на отсутствующие (s означает «без звука»).

2) Если вы создаете большее количество .dot файлов с непредсказуемыми именами, вам необходимо выполнить некоторую предварительную обработку. Создайте скрипт оболочки 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 здесь, чтобы избежать большого количества свободного места в получающемся объединенном файле):

### 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
}
}
0 голосов
/ 14 ноября 2018

Я использовал библиотеку Java для выполнения слияния и многое другое!

С помощью библиотеки я мог легко подключиться к структуре данных, изменить узлы, если это необходимо, и добавить атрибуты на график.

Быстрый пример в Котлине:

// 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"))
    }
0 голосов
/ 08 ноября 2018

Если это действительно незначительное редактирование объединенных входных файлов, тогда 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
  }
}
...