Mesclando gráficos em Graphviz

votos
1

Eu tenho uma coleção de dígrafos codificados em linguagem DOT, e eu quero fundi-los em um único digraph onde nós com o mesmo nome em diferentes gráficos de entrada são mesclados.

Por exemplo dado os seguintes arquivos:

1.dot:

digraph {
    A -> B
    A -> C
}

2.dot:

digraph {
    D -> E
    E -> F
}

3.dot:

digraph {
    D -> G
    G -> A
}

Gostaria de obter o seguinte result.dot:

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

Eu tentei usar gvpackmas renomeia nós duplicados.

> 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;
        }
}

Eu encontrei uma pergunta semelhante no SO que sugiro usar sedpara renomear os nós renomeados, mas que não parece muito limpo.

Existe uma maneira de mesclar os gráficos do jeito que eu gostaria que eles?

Publicado 08/11/2018 em 06:32
fonte usuário
Em outras línguas...                            


3 respostas

votos
2

Para exatamente a situação que você está descrevendo, usando os arquivos de amostra fornecidos, há uma resposta simples usando M4 - uma ferramenta padrão GNU Linux que deve ser instalado por padrão na maioria das distribuições.

Crie um arquivo merge123.m4com este conteúdo:

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

e executá-lo com o comando

m4 merge123.m4 > 123.dot

ea resultante 123.dotdo arquivo será

digraph 123 {

subgraph {
    A -> B
    A -> C
}

subgraph {
    D -> E
    E -> F
}

subgraph {
    D -> G
    G -> A
}

}

Se você não gostar das linhas vazias, feche cada linha no script com dnl(o builtin dnlsignifica “Descartar para a próxima linha” :), por exemplo

include(1.dot)dnl

m4é extremamente útil, pois adiciona recursos para graphvizque sejam realmente úteis para projetos mais envolvidos; ver também esta questão SO .

EDITADO para responder à pergunta no comentário:

Se você precisar incluir arquivos e não sabe o seu número e nomes, você tem (pelo menos) duas opções:

1) Se o número de arquivos é bastante pequeno e você sabe todos os nomes que eles poderiam ter, você pode sinclude()todos eles:

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

m4só vai incluir os arquivos que realmente existem, e não reclamar sobre os desaparecidos (os smeios "silencioso").

2) Se você produzir um número maior de .dotarquivos com nomes imprevisíveis, você vai precisar fazer alguns pré-processamento. Criar um shell script include.shsemelhante a este

#!/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.txtagora fornece m4as informações necessárias:

include(1.dot)dnl
include(2.dot)dnl
include(3.dot)dnl

Agora modifique o seu merge.m4arquivo, permitindo-lhe fazer uso da lista de arquivos fornecidos (eu estou adicionando dnlaqui para evitar um monte de espaço vazio no arquivo mesclado resultante):

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

A fim de manter o arquivo resultante separado dos arquivos de entrada, melhor usar uma extensão diferente ao mesclar:

m4 merge.m4 > merged.gv

que agora parece

### merge dot files
digraph 123 {
subgraph {
    A -> B
    A -> C
}
subgraph {
    D -> E
    E -> F
}
subgraph {
    D -> G
    G -> A
}
}
Respondeu 08/11/2018 em 08:50
fonte usuário

votos
0

Acabei usando uma biblioteca Java para executar a fusão, e muito mais!

Com a biblioteca eu poderia facilmente bater na estrutura de dados, alterar os nós se necessário, e adicionar atributos para o gráfico.

Um rápido exemplo, em 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"))
    }
Respondeu 14/11/2018 em 06:56
fonte usuário

votos
0

Se ele é realmente apenas uma edição menor dos arquivos de entrada se juntou, então perl é um ajuste natural:

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;

Então

$ 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
  }
}
Respondeu 08/11/2018 em 06:47
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more