001: package prefuse.action.layout;
002:
003: import java.awt.geom.Point2D;
004: import java.awt.geom.Rectangle2D;
005: import java.util.Iterator;
006:
007: import prefuse.Constants;
008: import prefuse.visual.NodeItem;
009: import prefuse.visual.VisualItem;
010: import prefuse.visual.expression.StartVisiblePredicate;
011:
012: /**
013: * Layout Action that sets the positions for newly collapsed or newly
014: * expanded nodes of a tree. This action updates positions such that
015: * nodes flow out from their parents or collapse back into their parents
016: * upon animated transitions.
017: *
018: * @author <a href="http://jheer.org">jeffrey heer</a>
019: */
020: public class CollapsedSubtreeLayout extends Layout {
021:
022: private int m_orientation;
023: private Point2D m_point = new Point2D.Double();
024:
025: /**
026: * Create a new CollapsedSubtreeLayout. By default, nodes will collapse
027: * to the center point of their parents.
028: * @param group the data group to layout (only newly collapsed or newly
029: * expanded items will be considered, as determined by their current
030: * visibility settings).
031: */
032: public CollapsedSubtreeLayout(String group) {
033: this (group, Constants.ORIENT_CENTER);
034: }
035:
036: /**
037: * Create a new CollapsedSubtreeLayout.
038: * @param group the data group to layout (only newly collapsed or newly
039: * expanded items will be considered, as determined by their current
040: * visibility settings).
041: * @param orientation the layout orientation, determining which point
042: * nodes will collapse/expand from. Valid values are
043: * {@link prefuse.Constants#ORIENT_CENTER},
044: * {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
045: * {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
046: * {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
047: * {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
048: */
049: public CollapsedSubtreeLayout(String group, int orientation) {
050: super (group);
051: m_orientation = orientation;
052: }
053:
054: // ------------------------------------------------------------------------
055:
056: /**
057: * Get the layout orientation, determining which point nodes will collapse
058: * or exapnd from. Valid values are
059: * {@link prefuse.Constants#ORIENT_CENTER},
060: * {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
061: * {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
062: * {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
063: * {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
064: * @return the layout orientation
065: */
066: public int getOrientation() {
067: return m_orientation;
068: }
069:
070: /**
071: * Set the layout orientation, determining which point nodes will collapse
072: * or exapnd from. Valid values are
073: * {@link prefuse.Constants#ORIENT_CENTER},
074: * {@link prefuse.Constants#ORIENT_LEFT_RIGHT},
075: * {@link prefuse.Constants#ORIENT_RIGHT_LEFT},
076: * {@link prefuse.Constants#ORIENT_TOP_BOTTOM}, and
077: * {@link prefuse.Constants#ORIENT_BOTTOM_TOP}.
078: * @return the layout orientation to use
079: */
080: public void setOrientation(int orientation) {
081: if (orientation < 0
082: || orientation >= Constants.ORIENTATION_COUNT)
083: throw new IllegalArgumentException(
084: "Unrecognized orientation value: " + orientation);
085: m_orientation = orientation;
086: }
087:
088: // ------------------------------------------------------------------------
089:
090: /**
091: * @see prefuse.action.Action#run(double)
092: */
093: public void run(double frac) {
094: // handle newly expanded subtrees - ensure they emerge from
095: // a visible ancestor node
096: Iterator items = m_vis.visibleItems(m_group);
097: while (items.hasNext()) {
098: VisualItem item = (VisualItem) items.next();
099: if (item instanceof NodeItem && !item.isStartVisible()) {
100: NodeItem n = (NodeItem) item;
101: Point2D p = getPoint(n, true);
102: n.setStartX(p.getX());
103: n.setStartY(p.getY());
104: }
105: }
106:
107: // handle newly collapsed nodes - ensure they collapse to
108: // the greatest visible ancestor node
109: items = m_vis.items(m_group, StartVisiblePredicate.TRUE);
110: while (items.hasNext()) {
111: VisualItem item = (VisualItem) items.next();
112: if (item instanceof NodeItem && !item.isEndVisible()) {
113: NodeItem n = (NodeItem) item;
114: Point2D p = getPoint(n, false);
115: n.setStartX(n.getEndX());
116: n.setStartY(n.getEndY());
117: n.setEndX(p.getX());
118: n.setEndY(p.getY());
119: }
120: }
121:
122: }
123:
124: private Point2D getPoint(NodeItem n, boolean start) {
125: // find the visible ancestor
126: NodeItem p = (NodeItem) n.getParent();
127: if (start)
128: for (; p != null && !p.isStartVisible(); p = (NodeItem) p
129: .getParent())
130: ;
131: else
132: for (; p != null && !p.isEndVisible(); p = (NodeItem) p
133: .getParent())
134: ;
135: if (p == null) {
136: m_point.setLocation(n.getX(), n.getY());
137: return m_point;
138: }
139:
140: // get the vanishing/appearing point
141: double x = start ? p.getStartX() : p.getEndX();
142: double y = start ? p.getStartY() : p.getEndY();
143: Rectangle2D b = p.getBounds();
144: switch (m_orientation) {
145: case Constants.ORIENT_LEFT_RIGHT:
146: m_point.setLocation(x + b.getWidth(), y);
147: break;
148: case Constants.ORIENT_RIGHT_LEFT:
149: m_point.setLocation(x - b.getWidth(), y);
150: break;
151: case Constants.ORIENT_TOP_BOTTOM:
152: m_point.setLocation(x, y + b.getHeight());
153: break;
154: case Constants.ORIENT_BOTTOM_TOP:
155: m_point.setLocation(x, y - b.getHeight());
156: break;
157: case Constants.ORIENT_CENTER:
158: m_point.setLocation(x, y);
159: break;
160: }
161: return m_point;
162: }
163:
164: } // end of class CollapsedSubtreeLayout
|