package graph.layout;
import graph.*;
import java.util.*;
/**
* A force-directed placement algorithm originally implemented
* by Úlfar Erlingsson at Cornell/RPI and modified to fit into
* this system.
*
* @author Michael Shilman (michaels@eecs.berkeley.edu)
* @version $Id$
*/
class LevelInfo {
boolean hasDummies = false;
double barycenter;
int level = -1;
int useage = 1000; //XXX
Node metaRoot = null;
boolean mark = false;
}
public class LevelLayout implements Action {
double m_l = 500;
int m_maxLevel = -1;
Vector m_levels[] = null;
public static int s_levelIndex = AttributeManager.NO_INDEX;
public void apply(Graph g) {
init(g);
makeLevels(g);
placeNodes(); //XXX
} //XXX
public void step(Graph g) { }
public void finish(Graph g) {} //XXX
public LevelLayout() {
if(s_levelIndex == AttributeManager.NO_INDEX) {
s_levelIndex = AttributeManager.getIndex("Level");
}
}
public void init(Graph g) {
addLevelAttrs(g);
preprocess(g);
addDummies(g);
}
/**
* Add a LevelInfo to the graph and each node in the
* graph.
*/
void addLevelAttrs(Graph g) {
LevelInfo linfo = (LevelInfo)g.getAttr(s_levelIndex);
if(linfo == null) {
linfo = new LevelInfo();
g.setAttr(s_levelIndex, linfo);
}
for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) {
Node n = (Node)e.nextElement();
LevelInfo inf = (LevelInfo)n.getAttr(s_levelIndex);
if(inf == null) {
inf = new LevelInfo();
n.setAttr(s_levelIndex, inf);
}
}
}
/**
* Add this node and all of its parent nodes to the
* levels array.
*/
void initialOrderNodes(Node n) {
setMark(n, true);
for(Enumeration ins = n.in.elements(); ins.hasMoreElements();) {
Edge e = (Edge)ins.nextElement();
if(!getMark(e.tail)) {
initialOrderNodes(e.tail);
}
}
m_levels[getLevel(n)].addElement(n);
}
/**
* Add and remove dummy nodes between nodes between levels that
* are far apart.
*/
void addDummies(Graph g) {
LevelInfo linfo = (LevelInfo)g.getAttr(s_levelIndex);
if(linfo.hasDummies) { return; }
linfo.hasDummies = true;
for(Enumeration nodes = g.nodes.elements(); nodes.hasMoreElements();) {
Node to = (Node)nodes.nextElement();
if(to instanceof DummyNode) { continue; }
LevelInfo nlinfo = (LevelInfo)to.getAttr(s_levelIndex);
for(Enumeration in = to.in.elements(); in.hasMoreElements();) {
Edge e = (Edge)in.nextElement();
if(e.tail instanceof DummyNode) { continue; }
while( getLevel(to) > getLevel(e.tail)+1 ) {
//dummy gets stuck between e.tail and e.head
Node dummy = new DummyNode();
LevelInfo dumInfo = new LevelInfo();
dumInfo.level = getLevel(e.tail)+1;
dummy.setAttr(s_levelIndex, dumInfo);
g.add(dummy);
e.swapHead(dummy);
//e is reassigned to make the while() work
try {
e = dummy.attach(to);
}
catch(Exception ex) {
System.out.println(ex);
System.exit(0);//XXX
}
}
}
}
}
/**
* Get the level of n in the graph.
* Requires that all nodes have LevelInfo attributes.
*/
void makeLevels(Graph g) {
m_maxLevel = -1;
Node maxNode = null;
int level;
//find the topmost node
for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) {
Node n = (Node)e.nextElement();
if((level = getLevel(n)) > m_maxLevel) {
m_maxLevel = level;
maxNode = n;
}
}
//create some buckets to store the nodes
m_levels = new Vector[m_maxLevel+1];
for(int i = 0; i < m_maxLevel+1; i++) {
m_levels[i] = new Vector();
}
//clear all the nodes
markAll(g, false);
//initialize
initialOrderNodes(maxNode);
for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) {
Node n = (Node)e.nextElement();
if(!getMark(n)) {
initialOrderNodes(n);
}
}
}
/**
* Place all the nodes on a level. Invoke this after the nodes
* have been assigned to their levels, and have been sorted properly.
*/
void placeLevel(double l, double y, Vector nodes ) {
double xstep = l/(nodes.size()+1);
int i = 0;
for(Enumeration e = nodes.elements(); e.hasMoreElements();) {
Node n = (Node)e.nextElement();
n.x = xstep*(++i);
n.y = y;
}
}
/**
* Invoke placeLevel on each level in the graph.
*/
void placeNodes() {
double ystep = m_l/(m_maxLevel+1);
double y = 0.0;
for(int i = 0; i <= m_maxLevel; i++ ) {
Vector nodes = m_levels[i];
placeLevel(m_l, y, nodes);
y += ystep;
}
}
/**
* Do insertion sort on the level based on the barycenters,
* then reorder
*/
protected final void sortLevel( Vector nodes ) {
int len = nodes.size();
for(int i = 1; i < len; i++) {
Node n1 = (Node) nodes.elementAt(i);
double barycent = barycenter(n1);
int j;
for(j = i; j > 0; j--) {
Node n2 = (Node)nodes.elementAt( j-1 );
if( barycent >= barycenter(n2)) break;
nodes.setElementAt(n2, j );
}
nodes.setElementAt(n1, j);
}
}
protected final void orderLevel( Vector nodes, double l, double y,
boolean doin, boolean doout ) {
int levelcnt = nodes.size();
for(Enumeration e = nodes.elements(); e.hasMoreElements();) {
Node n = (Node) e.nextElement();
computeBarycenter(n, doin, doout);
}
sortLevel( nodes );
placeLevel( l, y, nodes );
}
// Do downwards barycentering on first pass, upwards on second, then average
protected synchronized final void orderNodes( double l, int op ) {
boolean doup = ((op & 0x1) == 1);
boolean doin = (op > 5 || !doup);
boolean doout = (op > 5 || doup);
double ystep = (m_maxLevel>0) ? (m_l/m_maxLevel) : 0.0;
if( doup ) {
double y = 0.0;
for( int i = 0; i <= m_maxLevel; ++i ) { // Going upwards
Vector nodes = m_levels[i];
orderLevel( nodes, l, y, doin, doout );
y += ystep;
}
}
else {
double y = l;
for( int i = m_maxLevel; i >= 0; --i ) { // Going downwards
Vector nodes = m_levels[i];
orderLevel( nodes, l, y, doin, doout );
y -= ystep;
}
}
}
protected final void straightenDummy( Node n ) {
Node tail = ((Edge) n.in.firstElement()).tail;
Node head = ((Edge) n.in.firstElement()).head;
double avg = (n.x + tail.x + head.x) / 3;
n.x = avg;
}
private final int xmarginSize = 10;
protected synchronized final void straightenLayout( double l ) {
double ystep = l/(m_maxLevel+1);
double y = 0.0;
for(int i = 0; i <= m_maxLevel; i++) {
Vector nodes = m_levels[i];
for(Enumeration e = nodes.elements(); e.hasMoreElements(); ) {
Node n = (Node)e.nextElement();
if(n instanceof DummyNode) {
straightenDummy(n);
}
}
for(int j = 1; j < nodes.size(); j++) {
Node n = (Node)nodes.elementAt(j);
Node prev = (Node)nodes.elementAt( j-1 );
double prevright = prev.x + prev.w/2 + xmarginSize;
double thisleft = n.x - n.w/2 - xmarginSize;
double overlap = prevright - thisleft;
if( overlap > 0 ) {
prev.x = prev.x - overlap/2;
n.x = n.x + overlap/2;
}
n.y = y;
}
y += ystep;
}
}
/* XXX
protected int _operation = 0;
protected final int _Order = 100;
public final void Embed() {
double L = _bb.globals.L();
_bb.setArea( 0, 0, L, L );
if( _operation < _Order ) {
orderNodes( L, _operation );
}
else {
straightenLayout( L );
}
_bb.Update();
++_operation;
_bb.globals.Temp( (double)_operation );
}
*/
//==================================================================
// UTILITY FUNCTIONS HERE
//==================================================================
void computeBarycenter(Node n, boolean doin, boolean doout) {
double insum = 0.0;
int insize = 0;
int outsize = 0;
if(doin) {
insize = n.in.size();
for(Enumeration e = n.in.elements(); e.hasMoreElements();) {
insum += ((Edge)e.nextElement()).tail.x;
}
if(insize == 0) {
insize = 1;
insum = n.x;
}
}
double outsum = 0.0;
if( doout ) {
outsize = n.out.size();
for(Enumeration e = n.out.elements(); e.hasMoreElements();) {
outsum += ((Edge)e.nextElement()).head.x;
}
if(outsize == 0) {
outsize = 1;
outsum = n.x;
}
}
double barycenter;
if( doin && doout ) {
barycenter = (insum+outsum)/(insize+outsize);
}
else if( doin ) {
barycenter = insum/insize;
}
else if( doout ) {
barycenter = outsum/outsize;
}
else {
barycenter = n.x;
}
LevelInfo info = (LevelInfo)n.getAttr(s_levelIndex);
info.barycenter = barycenter;
}
double barycenter(Node n) {
LevelInfo info = (LevelInfo)n.getAttr(s_levelIndex);
return info.barycenter;
}
/**
* Mark all the nodes in the graph as val.
* Requires that all nodes have LevelInfo attributes.
*
* @param val The value to set the mark.
*/
void markAll(Graph g, boolean val) {
setMark(g, val);
for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) {
setMark((Node)e.nextElement(), false);
}
}
/**
* Mark a node as val.
* Requires that node have LevelInfo attributes.
*/
void setMark(Node n, boolean val) {
LevelInfo linfo = (LevelInfo)n.getAttr(s_levelIndex);
linfo.mark = val;
}
/**
* Get the value of n's mark.
* Requires that node has LevelInfo attributes.
*/
boolean getMark(Node n) {
LevelInfo linfo = (LevelInfo)n.getAttr(s_levelIndex);
return linfo.mark;
}
/**
* Get the level of n in the graph.
* Requires that node has LevelInfo attribute.
*/
int getLevel(Node n) {
LevelInfo inf = (LevelInfo)n.getAttr(s_levelIndex);
return inf.level;
}
/**
* Set the level of n in the graph.
* Requires that node has LevelInfo attribute.
*/
void setLevel(Node n, int l) {
LevelInfo inf = (LevelInfo)n.getAttr(s_levelIndex);
inf.level = l;
}
/**
* Get the level of n in the graph.
* Requires that node has LevelInfo attribute.
*/
int getUseage(Node n) {
LevelInfo inf = (LevelInfo)n.getAttr(s_levelIndex);
return inf.useage;
}
/**
* Set the level of n in the graph.
* Requires that node has LevelInfo attribute.
*/
void setUseage(Node n, int val) {
LevelInfo inf = (LevelInfo)n.getAttr(s_levelIndex);
inf.useage = val;
}
void preprocess(Graph g) {
markAll(g,false);
Node meta = makeMeta(g);
Vector topo = new Vector();
topoSort(meta, topo);
removeMeta(g);
int maxlevel = 0;
int level = 0;
for(Enumeration e = topo.elements(); e.hasMoreElements();) {
Node n1 = (Node)e.nextElement();
for(Enumeration ins = n1.in.elements(); ins.hasMoreElements();) {
Node n2 = ((Edge)ins.nextElement()).tail;
if(getLevel(n2) > level) {
level = getLevel(n2);
}
}
setLevel(n1, level+1);
if(level+1 > maxlevel) {
maxlevel = level+1;
}
}
for(int i = topo.size()-1; i >= 0; i--) {
Node n1 = (Node)topo.elementAt(i);
int minUseage = maxlevel;
int outedgecnt = n1.out.size();
if(n1.out.size() == 0 ) {
minUseage = getLevel(n1);
}
for(Enumeration e = n1.out.elements(); e.hasMoreElements(); ) {
Node n2 = ((Edge)e.nextElement()).tail;
if((getUseage(n2)-1) < minUseage) {
minUseage = getUseage(n2) - 1;
}
}
setUseage(n1, minUseage);
}
for(Enumeration e = topo.elements(); e.hasMoreElements();) {
Node n = (Node)e.nextElement();
setLevel(n, getUseage(n));
}
}
void topoSort(Node n, Vector topo) {
setMark(n, true);
for(Enumeration e = n.in.elements(); e.hasMoreElements();) {
Node n2 = ((Edge)e.nextElement()).tail;
if(!getMark(n2)) {
topoSort(n2, topo);
}
}
topo.addElement(n);
}
Node makeMeta(Graph g) {
Node meta = new Node();
meta.name = "meta";
meta.setAttr(s_levelIndex, new LevelInfo());
LevelInfo inf = (LevelInfo)g.getAttr(s_levelIndex);
inf.metaRoot = meta;
for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) {
Node n2 = (Node)e.nextElement();
try {
n2.attach(meta);
}
catch(Exception ex) {
System.out.println(ex);
System.exit(0);
}
}
g.nodes.addElement(meta);
return meta;
}
void removeMeta(Graph g) {
LevelInfo inf = (LevelInfo)g.getAttr(s_levelIndex);
g.delete(inf.metaRoot);
inf.metaRoot = null;
}
}