package prefuse.action.layout;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import prefuse.data.Node;
import prefuse.data.tuple.TupleSet;
import prefuse.visual.VisualItem;
/**
* Implements a uniform grid-based layout. This component can either use
* preset grid dimensions or analyze a grid-shaped graph to determine them
* automatically.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class GridLayout extends Layout {
protected int rows;
protected int cols;
protected boolean analyze = false;
/**
* Create a new GridLayout without preset dimensions. The layout will
* attempt to analyze an input graph to determine grid parameters.
* @param group the data group to layout. In this automatic grid
* analysis configuration, the group <b>must</b> resolve to a set of
* graph nodes.
*/
public GridLayout(String group) {
super(group);
analyze = true;
}
/**
* Create a new GridLayout using the specified grid dimensions. If the
* input data has more elements than the grid dimensions can hold, the
* left over elements will not be visible.
* @param group the data group to layout
* @param nrows the number of rows of the grid
* @param ncols the number of columns of the grid
*/
public GridLayout(String group, int nrows, int ncols) {
super(group);
rows = nrows;
cols = ncols;
analyze = false;
}
/**
* @see prefuse.action.Action#run(double)
*/
public void run(double frac) {
Rectangle2D b = getLayoutBounds();
double bx = b.getMinX(), by = b.getMinY();
double w = b.getWidth(), h = b.getHeight();
TupleSet ts = m_vis.getGroup(m_group);
int m = rows, n = cols;
if ( analyze ) {
int[] d = analyzeGraphGrid(ts);
m = d[0]; n = d[1];
}
Iterator iter = ts.tuples();
// layout grid contents
for ( int i=0; iter.hasNext() && i < m*n; ++i ) {
VisualItem item = (VisualItem)iter.next();
item.setVisible(true);
double x = bx + w*((i%n)/(double)(n-1));
double y = by + h*((i/n)/(double)(m-1));
setX(item,null,x);
setY(item,null,y);
}
// set left-overs invisible
while ( iter.hasNext() ) {
VisualItem item = (VisualItem)iter.next();
item.setVisible(false);
}
}
/**
* Analyzes a set of nodes to try and determine grid dimensions. Currently
* looks for the edge count on a node to drop to 2 to determine the end of
* a row.
* @param ts TupleSet ts a set of nodes to analyze. Contained tuples
* <b>must</b> implement be Node instances.
* @return a two-element int array with the row and column lengths
*/
public static int[] analyzeGraphGrid(TupleSet ts) {
// TODO: more robust grid analysis?
int m, n;
Iterator iter = ts.tuples(); iter.next();
for ( n=2; iter.hasNext(); n++ ) {
Node nd = (Node)iter.next();
if ( nd.getDegree() == 2 )
break;
}
m = ts.getTupleCount() / n;
return new int[] {m,n};
}
/**
* Get the number of grid columns.
* @return the number of grid columns
*/
public int getNumCols() {
return cols;
}
/**
* Set the number of grid columns.
* @param cols the number of grid columns to use
*/
public void setNumCols(int cols) {
this.cols = cols;
}
/**
* Get the number of grid rows.
* @return the number of grid rows
*/
public int getNumRows() {
return rows;
}
/**
* Set the number of grid rows.
* @param rows the number of grid rows to use
*/
public void setNumRows(int rows) {
this.rows = rows;
}
} // end of class GridLayout