Vitor Pamplona

Innovation on Vision: Imaging , Enhancement and Simulation

Prefuse: Visualização de Grafos em Java

Prefuse é uma biblioteca para modelagem, visualização e interação de grafos, tabelas e árvores em Java. Com estruturas de dados otimizada o framework gerencia layouts de visualização, técnicas de codificação visual, animação, queries dinâmicas, pesquisas integradas e conexão com banco de dados. Disponível sob BSD, o Prefuse usa Java2D para visualização e pode ser utilizado tanto em Applets quanto em aplicações Swing / AWT.  

Entre os layouts mais interessantes, destacam-se a visualização e interação de grafos e árvores e o layout em círculo. Cliquem nas imagens abaixo para executarem a demonstração de cada um deles


 

O framework é a base arquitetural com seus componentes distribuídos em um padrão MVC estendido. A interface da biblioteca não é muito simples, e falta muita documentação, mas a boa arquitetura permite muitas extensões em qualquer parte do MVC desenvolvido. Abaixo, mostrarei a arquitetura da Prefuse com a construção de uma aplicação exemplo.  

Primeiramente, baixe a Prefuse, compile-a utilizando o build.sh ou adicione o diretório raiz como projeto no Eclipse.  

O exemplo que iremos desenvolver é a visualização de grafos simples, a última das imagens acima.  

Carregando o Grafo

O Prefuse utiliza uma estrutura própria para armazenamento de dados. Desta forma você terá que converter o seu formato ou estrutura de dados para o deles. Dentro do zip que você baixou, no diretório data, há um arquivo chamado socialnet.xml que possui um exemplo de grafo já na estrutura deles.  

Você pode carregar diretamente este arquivo para o exemplo

Graph graph = null;
try {
graph = new GraphMLReader().readGraph("/socialnet.xml");
} catch ( DataIOException e ) {
e.printStackTrace();
System.err.println("Error loading graph. Exiting...");
System.exit(1);
}

ou pode criar seus próprios dados. Neste caso, para criar um grafo e passar as informações necessárias a cada nodo, é necessário definir quais os atributos que serão utilizados tanto para nodos quanto para arestas. O construtor da classe Graph leva cinco atributos como parâmetro: Uma especificação dos dados em cada nodo, uma especificação dos dados em cada aresta, se o grafo é dirigido ou não, e os dois atributos em cada aresta que possuirão os identificadores dos Nodos adjacentes. Abaixo, um exemplo:  

Table table = new Table();
table.addColumn("id", int.class);
table.addColumn("keyword", String.class);
table.addColumn("hasDefinition", int.class);

Table edges = new Table();
edges.addColumn("id1", int.class);
edges.addColumn("id2", int.class);

Graph graph = new Graph(table, edges, true, "id1", "id2");

Na Prefuse não se adiciona nodos no grafo. O grafo é quem cria os nodos através do método addNode , você deve apenas adicionar os parâmetros necessários. Abaixo um exemplo de como adicionar nodos no grafo utilizando a estrutura de dados do Priki, em Prevayler, com os dados deste blog. Para quem não sabe, cada texto deste blog é armazenado como uma lista encadeada de palavras e frases. Cada palavra ou frase pode possuir uma definição (texto) e possui referências aos textos que está sendo utilizada. No trecho de código, estou enviando ao Prefuse todas as tags com seus títulos das páginas do blog como nodos e criando uma aresta para ligar tag e título.

HashMap<String,Node> nodeList = new HashMap<String,Node>();
       
for (Wikiword w : wiki.getGlobalTags()) {
      Node t = nodeList.get(w.getKeyword());
           
      if (t == null) {
        t = createNode(graph, w);
          nodeList.put(w.getKeyword(), t);
      }                   
           
      for (Wikiword related : w.getRelated()) {
           Node s = nodeList.get(related.getKeyword());
               
           if (s == null) {
               s = createNode(graph, related);
               nodeList.put(related.getKeyword(), s);
           }       
               
           graph.addEdge(s, t);
      }

O método createNode transforma uma wikiword em um Nodo do grafo da Prefuse. Repare que você deve passar os atributos de cada nodo como se fosse um HashMap.  

public Node createNode(Graph g, Wikiword w) {
Node n = g.addNode();
n.set("id", w.getKeyword().hashCode());
n.set("keyword", w.getKeyword());
n.set("hasDefinition", w.hasDefinition() ? 1 : 0);
return n;

Visualização

Vamos agora criar uma abstração visual para o grafo. Crie uma instância da classe Visualization e adicione o grafo com um nome. Este nome será importante pois vamos referenciá-lo em seguida. A visualização cria duas novas referências: nome +. nodes para os nós do grafo e nome +. edges para as arestas.  

// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add("graph", graph);

Renderizadores

Próximo passo é dizer para a biblioteca como queremos que ela desenhe os nós e arestas do grafo que já está adicionado na visualização. No nosso caso, vamos criar um renderizador que vai escrever o nome de uma propriedade do nó ( Keyword ) centralizado em um retângulo com cantos arredondados.

// **** Trocar o keyword por name se for usar o arquivo xml  *** 
LabelRenderer r = new LabelRenderer("keyword");
r.setRoundedCorner(8, 8); // round the corners
r.setRenderType(AbstractShapeRenderer.RENDER_TYPE_FILL);
r.setHorizontalAlignment(Constants.CENTER);

// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
vis.setRendererFactory(new DefaultRendererFactory(r));

Processamento Visual

Vamos agora adicionar um conjunto de ações que serão executadas a cada render do grafo. Elas definirão as cores dos nós e arestas de acordo com sua informação, como será a animação entre outros. Primeiro vamos criar uma paleta de cores para que a cor do retângulo seja azul quando o nó for uma página do blog e vermelha quando for uma tag.  

int[] palette = new int[] { ColorLib.rgb(255,180,180), ColorLib.rgb(190,190,255) };

Associamos, então, esta paleta ao grupo de nós

// map nominal data values to colors using our provided palette
// **** Trocar o hasDefinition por gender se for usar o arquivo xml ***
DataColorAction fill = new DataColorAction("graph.nodes", "hasDefinition",Constants.NOMINAL, VisualItem.FILLCOLOR, palette);

Definimos as cores do texto dos nós e da linha das arestas:  

// use black for node text
ColorAction text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0));
// use light grey for edges
ColorAction edges = new ColorAction("graph.edges", VisualItem.STROKECOLOR, ColorLib.gray(100));

E adicionamos estas ações em um chain:  

ActionList color = new ActionList();
color.add(fill);
color.add(text);
color.add(edges);

// add the actions to the visualization
vis.putAction("color", color);

Layout dos Dados e Animação

Para controlar o layout faremos algo semelhante ao controle visual da seção anterior. Criamos um chain indicando que a animação nunca vai parar.  

ActionList layout = new ActionList(Activity.INFINITY);
layout.add(new ForceDirectedLayout("graph", true));
layout.add(new RepaintAction());
vis.putAction("layout", layout);

Mecanismos de Interação  

Por fim, criamos um Display para mostrar os dados e configuramos as ações de Drag, Pan, Zoom, WheelZoom, ZoomToFit e HeighborHighliht sobre este display.  

Display display = new Display(vis);
display.setSize(700,700); // Tamanho do display
display.pan(350, 350); // Configura o centro do Display.
display.setForeground(Color.GRAY);
display.setBackground(Color.WHITE);
display.addControlListener(new DragControl()); // drag items around
display.addControlListener(new PanControl());  // pan with background left-drag
display.addControlListener(new ZoomControl()); // zoom with vertical right-drag
display.addControlListener(new WheelZoomControl());
display.addControlListener(new ZoomToFitControl());
display.addControlListener(new NeighborHighlightControl());

Agora basta adicionar este display em um JFrame e rodar.

JFrame frame = new JFrame("prefuse example");
// ensure application exits when window is closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(display);
frame.pack(); // layout components in window
frame.setVisible(true); // show the window
vis.run("color");  // assign the colors
vis.run("layout"); // start up the animated layout

Como você pode notar, trabalhar com o Prefuse não é muito simples, mas ele te dará muito poder para escolher formas de renderização e interação apropriadas. Não fui muito feliz na minha visualização. O meu grafo é muito complexo, tornando-o difícil de ver.  

Para saber mais, consulte:

Posted in Aug 31, 2009 by Vitor Pamplona - Edit - History

Showing Comments

massa, vou fazer uns helloworld, valeu pela dica

- - Brenno Hayden

- - Posted in Aug 11, 2008 by 201.72.152.4

Cara,
Esse framework permite o cara capturar eventos do tipo onClick quando clicamos em um dos vértices do grafo?
Me manda um e-mail respondendo: diegovss@gmail.com

- - Diego

- - Posted in Jan 27, 2010 by 189.36.200.18

Otimo post!

Valeu mesmo!

- - Koiti

- - Posted in May 30, 2010 by 189.82.69.170

Tem como colocar nome nas arestas???

- - Felipe

- - Posted in Jul 28, 2010 by 201.9.254.103

Tem como colocar valores nas arestas??
Na parte carregando o grafo não consegui construir de forma manual o grafo, ou seja, contruir um grafo que não é lido pelo xml

- - Lailson

- - Posted in Oct 25, 2011 by 187.29.29.6

Vitor.. por favor pode me ajudar? preciso fazer gráfico de linha e tb de barras com PREFUSE...

- - Carlos

- - Posted in Nov 18, 2011 by 201.16.217.3

Cara muito bom = D, me ajudou muito essas explicações, vou usar essa biblioteca na minha iniciação científica

- - Gabriel

- - Posted in Nov 24, 2012 by 189.86.42.147

Add New Comment

Your Name:


Write the code showed above on the text below.