package prefuse.action.layout;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import prefuse.Constants;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.StartVisiblePredicate;
/**
* Layout Action that sets the positions for newly collapsed or newly
* expanded nodes of a tree. This action updates positions such that
* nodes flow out from their parents or collapse back into their parents
* upon animated transitions.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class CollapsedSubtreeLayout extends Layout {
private int m_orientation;
private Point2D m_point = new Point2D.Double();
/**
* Create a new CollapsedSubtreeLayout. By default, nodes will collapse
* to the center point of their parents.
* @param group the data group to layout (only newly collapsed or newly
* expanded items will be considered, as determined by their current
* visibility settings).
*/
public CollapsedSubtreeLayout(String group) {
this(group, Constants.ORIENT_CENTER);
}
/**
* Create a new CollapsedSubtreeLayout.
* @param group the data group to layout (only newly collapsed or newly
* expanded items will be considered, as determined by their current
* visibility settings).
* @param orientation the layout orientation, determining which point
* nodes will collapse/expand from. Valid values are
* {@link prefuse.Constants#ORIENT_CENTER},
* {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
* {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
* {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
* {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
*/
public CollapsedSubtreeLayout(String group, int orientation) {
super(group);
m_orientation = orientation;
}
// ------------------------------------------------------------------------
/**
* Get the layout orientation, determining which point nodes will collapse
* or exapnd from. Valid values are
* {@link prefuse.Constants#ORIENT_CENTER},
* {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
* {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
* {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
* {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
* @return the layout orientation
*/
public int getOrientation() {
return m_orientation;
}
/**
* Set the layout orientation, determining which point nodes will collapse
* or exapnd from. Valid values are
* {@link prefuse.Constants#ORIENT_CENTER},
* {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
* {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
* {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
* {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
* @return the layout orientation to use
*/
public void setOrientation(int orientation) {
if ( orientation < 0 || orientation >= Constants.ORIENTATION_COUNT )
throw new IllegalArgumentException(
"Unrecognized orientation value: "+orientation);
m_orientation = orientation;
}
// ------------------------------------------------------------------------
/**
* @see prefuse.action.Action#run(double)
*/
public void run(double frac) {
// handle newly expanded subtrees - ensure they emerge from
// a visible ancestor node
Iterator items = m_vis.visibleItems(m_group);
while ( items.hasNext() ) {
VisualItem item = (VisualItem) items.next();
if ( item instanceof NodeItem && !item.isStartVisible() ) {
NodeItem n = (NodeItem)item;
Point2D p = getPoint(n, true);
n.setStartX(p.getX());
n.setStartY(p.getY());
}
}
// handle newly collapsed nodes - ensure they collapse to
// the greatest visible ancestor node
items = m_vis.items(m_group, StartVisiblePredicate.TRUE);
while ( items.hasNext() ) {
VisualItem item = (VisualItem) items.next();
if ( item instanceof NodeItem && !item.isEndVisible() ) {
NodeItem n = (NodeItem)item;
Point2D p = getPoint(n, false);
n.setStartX(n.getEndX());
n.setStartY(n.getEndY());
n.setEndX(p.getX());
n.setEndY(p.getY());
}
}
}
private Point2D getPoint(NodeItem n, boolean start) {
// find the visible ancestor
NodeItem p = (NodeItem)n.getParent();
if ( start )
for (; p!=null && !p.isStartVisible(); p=(NodeItem)p.getParent());
else
for (; p!=null && !p.isEndVisible(); p=(NodeItem)p.getParent());
if ( p == null ) {
m_point.setLocation(n.getX(), n.getY());
return m_point;
}
// get the vanishing/appearing point
double x = start ? p.getStartX() : p.getEndX();
double y = start ? p.getStartY() : p.getEndY();
Rectangle2D b = p.getBounds();
switch ( m_orientation ) {
case Constants.ORIENT_LEFT_RIGHT:
m_point.setLocation(x+b.getWidth(), y);
break;
case Constants.ORIENT_RIGHT_LEFT:
m_point.setLocation(x-b.getWidth(), y);
break;
case Constants.ORIENT_TOP_BOTTOM:
m_point.setLocation(x, y+b.getHeight());
break;
case Constants.ORIENT_BOTTOM_TOP:
m_point.setLocation(x, y-b.getHeight());
break;
case Constants.ORIENT_CENTER:
m_point.setLocation(x, y);
break;
}
return m_point;
}
} // end of class CollapsedSubtreeLayout