package prefuse.action.layout; import java.awt.geom.Rectangle2D; import java.util.Iterator; import prefuse.Constants; import prefuse.data.Table; import prefuse.render.PolygonRenderer; import prefuse.visual.VisualItem; /** * Layout Action that updates the outlines of polygons in a stacked line chart, * properly setting the coordinates of "collapsed" stacks. * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class CollapsedStackLayout extends Layout { private String m_polyField; private int m_orientation = Constants.ORIENT_BOTTOM_TOP; private boolean m_horiz = false; private boolean m_top = false; /** * Create a new CollapsedStackLayout. The polygon field is assumed to be * {@link prefuse.render.PolygonRenderer#POLYGON}. * @param group the data group to layout */ public CollapsedStackLayout(String group) { this(group, PolygonRenderer.POLYGON); } /** * Create a new CollapsedStackLayout. * @param group the data group to layout * @param field the data field from which to lookup the polygons */ public CollapsedStackLayout(String group, String field) { super(group); m_polyField = field; } /** * Returns the orientation of this layout. One of * {@link Constants#ORIENT_BOTTOM_TOP} (to grow bottom-up), * {@link Constants#ORIENT_TOP_BOTTOM} (to grow top-down), * {@link Constants#ORIENT_LEFT_RIGHT} (to grow left-right), or * {@link Constants#ORIENT_RIGHT_LEFT} (to grow right-left). * @return the orientation of this layout */ public int getOrientation() { return m_orientation; } /** * Sets the orientation of this layout. Must be one of * {@link Constants#ORIENT_BOTTOM_TOP} (to grow bottom-up), * {@link Constants#ORIENT_TOP_BOTTOM} (to grow top-down), * {@link Constants#ORIENT_LEFT_RIGHT} (to grow left-right), or * {@link Constants#ORIENT_RIGHT_LEFT} (to grow right-left). * @param orient the desired orientation of this layout * @throws IllegalArgumentException if the orientation value * is not a valid value */ public void setOrientation(int orient) { if ( orient != Constants.ORIENT_TOP_BOTTOM && orient != Constants.ORIENT_BOTTOM_TOP && orient != Constants.ORIENT_LEFT_RIGHT && orient != Constants.ORIENT_RIGHT_LEFT) { throw new IllegalArgumentException( "Invalid orientation value: "+orient); } m_orientation = orient; m_horiz = (m_orientation == Constants.ORIENT_LEFT_RIGHT || m_orientation == Constants.ORIENT_RIGHT_LEFT); m_top = (m_orientation == Constants.ORIENT_TOP_BOTTOM || m_orientation == Constants.ORIENT_LEFT_RIGHT); } /** * @see prefuse.action.Action#run(double) */ public void run(double frac) { VisualItem lastItem = null; Rectangle2D bounds = getLayoutBounds(); float floor = (float) (m_horiz ? (m_top?bounds.getMaxX():bounds.getMinX()) : (m_top?bounds.getMinY():bounds.getMaxY())); int bias = (m_horiz ? 0 : 1); // TODO: generalize this -- we want tuplesReversed available for general sets Iterator iter = ((Table)m_vis.getGroup(m_group)).tuplesReversed(); while ( iter.hasNext() ) { VisualItem item = (VisualItem)iter.next(); boolean prev = item.isStartVisible(); boolean cur = item.isVisible(); if ( !prev && cur ) { // newly visible, update contour float[] f = (float[])item.get(m_polyField); if ( f == null ) continue; if ( lastItem == null ) { // no previous items, smash values to the floor for ( int i=0; i<f.length; i+=2 ) f[i+bias] = floor; } else { // previous visible item, smash values to the // visible item's contour float[] l = (float[])lastItem.get(m_polyField); for ( int i=0; i<f.length/2; i+=2 ) f[i+bias] = f[f.length-2-i+bias] = l[i+bias]; } } else if ( prev && cur ) { // this item was previously visible, remember it lastItem = item; } } } } // end of class CollapsedStackAction