Prefuse, um framework extensível para visualização de Grafos

Na computação, os profissionais geralmente se deparam com problemas relacionados a grafos...

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, 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. 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.

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados