Na computação, os profissionais geralmente se deparam com problemas relacionados a grafos, seja a procura de uma distância mínima, com o famoso algoritmo de Dijkstra, seja buscando a classificação de um grafo como Euleriano, Hamiltoniano, ou até mesmo uma simples visualização de uma equipe de desenvolvimento. Independente de qual seja o tipo do problema, é necessário um certo cuidado com a visualização dessas informações. No entanto, a forma mais comum de representação grafos está na forma matricial, levando em consideração sua praticidade e facilidade, deixando a visualização a critério da criatividade do programador.

Neste artigo, será apresentado um framework que possibilita a visualização de grafos de uma forma bem interativa e divertida. Utilizando o *.graphml para armazenamento dos grafos.

O Prefuse

O Prefuse, de acordo com a própria definição, é um framework estensível para ajudar desenvolvedores de software a criarem aplicações de visualização de informações interativas usando a linguagem de programação Java.

Como começar

O Prefuse pode ser obtido por download, ou através do CVS (instruções online). Para utilizá-lo, baixei-o e coloque-o no seu path.

Existem diferentes maneiras para construir o seu grafo. A forma a ser apresentada necessitará basicamente de duas etapas. A primeira etapa é construir o *.graphml correspondente ao seu grafo. A segunda etapa é criar o algoritmo de desenho do próprio grafo.

A utilização do XML para gerar o grafo traz consigo uma grande portabilidade, visto que uma vez construída a visualização do grafo (etapa 2), será necessário apenas apontar o novo XML e o seu grafo já desenhado irá ser utilizado novamente, ou seja, trabalhando em camadas diferentes.

O GraphML é um formato de arquivo para grafos que é fácil de utilizar e permite a descrição de propriedades do grafo, como direcionado, não direcionado, mixto, hipergrafo, grafos hierárquicos, no entanto o GraphML não utiliza uma linguagem customizada, antes se baseia no XML para descrevê-lo.

Para aprender mais sobre o GraphML, visite o site The GraphML File Format.

Nas próximas sessões iremos aprender mais sobre o graphML e como visualizá-lo com o Prefuse.

GraphML

O graphML é descrito de forma bem simples. Como exemplo vamos olhar o seguinte arquivo disponível no próprio site:


<?xml version="1.0" encoding="UTF-8"?>

<graphml xmlns="http://graphml.graphdrawing.org/xmlns" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns

     http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">

  <graph id="G" edgedefault="undirected">

    <node id="n0"/>

    <node id="n1"/>

    <node id="n2"/>

    <node id="n3"/>

    <node id="n4"/>

    <node id="n5"/>

    <node id="n6"/>

    <node id="n7"/>

    <node id="n8"/>

    <node id="n9"/>

    <node id="n10"/>

    <edge source="n0" target="n2"/>

    <edge source="n1" target="n2"/>

    <edge source="n2" target="n3"/>

    <edge source="n3" target="n5"/>

    <edge source="n3" target="n4"/>

    <edge source="n4" target="n6"/>

    <edge source="n6" target="n5"/>

    <edge source="n5" target="n7"/>

    <edge source="n6" target="n8"/>

    <edge source="n8" target="n7"/>

    <edge source="n8" target="n9"/>

    <edge source="n8" target="n10"/>

  </graph>

</graphml>

Basicamente, informamos nesse exemplo que o grafo será não direcionado: "undirected".Terá os nodos variando de n0 até n10. Não especifica nenhum atributo para o nodo como Nome, ou qualquer outra coisa, a não ser pelo id, que é de suma importância, visto que é através do id que correlacionaremos os nodos.

Abaixo do Nodo #10 temos as arestas ligando o n0 ao n2, e assim por diante. Terminado esse processo, já podemos ir a segunda etapa.

É importante que o programador saiba como gerar o seu próprio arquivo XML, visto que as informações podem estar em uma base de dados, serem transmitidas de algum local, ou estar apenas em um vetor mesmo, o desenvolvedor deverá saber qual forma de manipular esses dados a fim de obter o arquivo necessário.

Passemos agora a criar o nosso primeiro grafo. Construí uma pequena classe que gera o XML. Chamei-a de GenerateXML.java E estarei explicando-a através dos comentários.


package com.googlepages.sfelixjr.grafo;

 

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

 

/**

 * Classe que gera o arquivo XML, base para o grafo que será criado pelo framework <a href="prefuse.org">prefuse</a>

 * @author Samuel Felix

 *

 */

public class GenerateXML {

       

        //Aqui definimos o cabecalho do arquivo xml, isso teoricamente nao muda, entao nao se preocupe

        public static StringBuffer arquivo = new StringBuffer(

                       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--  An excerpt of an egocentric social network  -->\n<graphml xmlns=\"").

        append("http://graphml.graphdrawing.org/xmlns\">\n\t<graph edgedefault=\"undirected\">\n\n\t\t<!-- data schema -->\n\t\t<key id=\"name\" for=\"node\"").

        append(" attr.name=\"name\" attr.type=\"string\"/>\n\t\t<key id=\"gender\" for=\"node\" attr.name=\"gender\" attr.type=\"string\"/>\n\n\t\t<!-- nodes -->  ");

       

        /**

         * Método utilizado para gerar os nodos,

         * será passado um id, para correlação nas arestas.

         * Através do id, criaremos uma aresta de um nodo a outro.

         * @param id

         * @param nome

         * @return

         */

        public static void generateNodes(long userId, String name, String gender){

               arquivo.append("\n\t\t<node id=\"").append(userId).append("\">\n\t\t\t").append(

               "<data key=\"name\">").append(name).append("</data>\n\t\t\t<data key=\"gender\">").append(gender).append("</data>\n\t\t</node>");

        }

       

        /**

         * Nessa método serão construídos as arestas correspondes ao id do usuário de origem (idSource)

         * ao id do usuário de destino (idTarget).

         * @param idSource

         * @param idTarget

         * @return

         */

        public static void generateEdges(int idSource, int idTarget){

               arquivo.append("\n\t\t<edge source=\"").append(idSource).append("\" target=\"").append(idTarget).append("\"></edge>\n\t\t\t");

        }

       

        /**

         * Esse método irá "fechar" o arquivo

         *

         */

        public static void fechaArquivo(){

               arquivo.append("\n\n\t</graph>\n</graphml>");

        }

       

        /**

         * Método utilizado para salvar o arquivo no disco

         * @param path

         */

        public static void salvarXML(String path)

        {

               try {

                       BufferedWriter out = new BufferedWriter(new FileWriter(path));

                       out.write(arquivo.toString());

                       out.flush();

                       out.close();

               } catch (IOException e) {

                       e.printStackTrace();

                       System.err.println("Erro ao salvar arquivo...Saindo");

                       System.exit(0);

               }

        }

}

Visualização

Gerado o XML, vamos construir a visualização com base no GraphView criado por:


<a href="http://jheer.org">jeffrey heer</a>

Na classe que chamei de VisualizadorDeGrafo.java, iremos fazer a leitura do arquivo XML:


/**

         * Metodo responsavel por "abrir" o xml

          */

        public Graph abrirXML(String nome) {

               Graph grafo = null;

               try {

                       grafo = new GraphMLReader()

                                      .readGraph(nome);

               } catch (DataIOException e) {

                       e.printStackTrace();

                       System.err.println("Erro ao ler grafo. Saindo...");

                       System.exit(1);

               }

               return grafo;

        }

O Prefuse possui um Reader próprio para o graphml. Ou seja, não precisamos nos preocupar em como ler esse arquivo, precisamos apenas apontá-lo, e ele fará a leitura para nós retornando um objeto da classe Graph. No construtor, iremos criar um objeto da classe Visualization, que é responsável em tratar a visualização na tela.


// Cria visualizacao

final Visualization vis = new Visualization();

Graph grafo = abrirXML(nome);

vis.add("graph", grafo);

// Essa linha indica qual atributo do XML irá ser escrito no nodo

LabelRenderer r = new LabelRenderer("name");

// Arredonda os cantos, quanto mais proximo de zero mais "quadrado" o

// nodo ira ficar

r.setRoundedCorner(8, 8);

vis.setRendererFactory(new DefaultRendererFactory(r));
Ao criarmos o renderer, utilizamos o método setRoundedCorner para determinarmos o tamanho dos nós.

Ao criarmos o renderer, utilizamos o método setRoundedCorner para determinarmos o tamanho dos nós. Quanto mais esse número se aproxima de zero, mais o nó se parecerá com um quadrado, quanto maior, mais parecido com um círculo. Se não for “setado”, ficariam apenas arestas. Com o setRendererFactory, setamos o LabelRenderer criado na visualização.

Vamos agora as cores. As cores serão colocadas em um vetor de inteiros, onde serão especificas através do padrão RGB. No vetor colocamos as cores verde e amarelo, algo um tanto quanto patriota.

Agora, uma pergunta a ser feita seria: Eu vou pintar o meu grafo em relação a que? Você estará livre a criar quantos atributos dos nodos desejar, e é em relação a esses atributos que iremos diferenciar em cores através do construtor do DataColorAction, passamos como argumento os nodos, e logo após o campo no nosso XML que desejamos colorir, no nosso exemplo o gênero. O genero M (Masculino) está de verde, enquanto que o F está de amarelo. Se acaso você desejar outras cores, instancie um JColorChooser e fique a vontade para ser criativo.


int[] paletaDeCores = new int[] {

        ColorLib.rgb(255,255,0),ColorLib.rgb(0,200,0) };

DataColorAction fill = new DataColorAction("graph.nodes", "gender",

        Constants.NOMINAL, VisualItem.FILLCOLOR, paletaDeCores);

//Utilizamos o preto para a cor do texto

ColorAction text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR,

        ColorLib.gray(0));

ColorAction edges = new ColorAction("graph.edges",

        VisualItem.STROKECOLOR, ColorLib.gray(200));

ActionList color = new ActionList();

color.add(fill);

color.add(text);

color.add(edges);

Agora, criaremos um ActionList para prover a Animação. O argumento Infinity diz que a ação irá rodar indefinidamente. Colocamos então o ForceDirectedLayout que irá posicionar os elementos do gravo:


ActionList layout = new ActionList(Activity.INFINITY);

layout.add(new ForceDirectedLayout("graph"));

layout.add(new RepaintAction());

vis.putAction("color", color);

vis.putAction("layout", layout);

Ao final colocaremos a visualização em um Display, e em um JFrame:


Display display = new Display(vis);

display.setSize(720, 500);

display.addControlListener(new DragControl()); // drag

display.addControlListener(new PanControl()); // pan

display.addControlListener(new ZoomControl()); // zoom com o clique direito do mouse

JFrame frame = new JFrame(nome);

JScrollPane o = new JScrollPane(display);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.add(o);

frame.pack();

frame.setVisible(true);

Agora, iremos usar tudo o que criamos e finalmente gerar o nosso grafo:


package com.googlepages.sfelixjr.grafo;

 

/**

 * Classe Demo para criação e visualização de Grafos usando o prefuse

 * @author Samuel Félix

 *

 */

public class MeuGrafo {

 

 

        //Coloquei alguns amigos aqui

        private String[] amigos = {"Samuel","Alba","Tux","Mili","Duke","KK","Sun","Lua"};

        //E pessoas famosas aqui

        private String[] famosos = {"Dijk","Tannen","Linu","Richard","Bill","C.S.Lewi"};

       

        public MeuGrafo(String nomeDoMeuGrafo){

              

               criarNodos();

               criarArestas();

               criaArquivo(nomeDoMeuGrafo);

              

        }

       

        /**

         * Finaliza e cria o XML

         */

        private void criaArquivo(String nomeDoMeuGrafo) {

               GenerateXML.fechaArquivo();

               GenerateXML.salvarXML(nomeDoMeuGrafo);

        }

 

        /**

         *

         * Criamos os nodos, veja que especificamos um id para cada nodo,

         * esse id ira identificar o ator quando criarmos as arestas

         * criei um criterio para dizer o genero do ator

         *

         */

        private void criarNodos(){

               //Vamos criar os nodos dos "amigos"

               for (int i = 0; i < amigos.length; i++)

                       GenerateXML.generateNodes(i*100+1, amigos[i], i%2==0?"M":"F");

               

               //Vamos criar os nodos dos "famosos"

               for (int i = 0; i < famosos.length; i++)

                       GenerateXML.generateNodes(i*100+5, famosos[i], "M");

        }

       

        /**

         * Neste metodo "ligamos" todos os amigos a todos os famosos.

         * Mas obviamente isso e algo ilustrativo.

         *

         */

        private void criarArestas(){

               for (int i = 0; i < amigos.length; i++) {

                       for (int j = 0; j < famosos.length; j++) {

                               GenerateXML.generateEdges(i*100+1, j*100+5);

                       }

               }

        }

       

        public static void main(String[] args) {

               //Cria o XML

               new MeuGrafo("grafo.xml");

               //Cria a Visualização Apontando o XML criado

               new VisualisadorDeGrafo("grafo.xml");

        }

}

O Prefuse é um framework muito poderoso, e esta é apenas uma das muitas visualizações disponíveis. Use sua criatividade e contribua com a comunidade Java. Espero que tenham gostado.