package prefuse.action.layout; import java.awt.geom.Rectangle2D; import java.util.Iterator; import prefuse.Constants; import prefuse.data.Table; import prefuse.data.Tuple; import prefuse.data.expression.Predicate; import prefuse.data.query.NumberRangeModel; import prefuse.data.query.ObjectRangeModel; import prefuse.data.tuple.TupleSet; import prefuse.util.DataLib; import prefuse.util.MathLib; import prefuse.util.ui.ValuedRangeModel; import prefuse.visual.VisualItem; /** * Layout Action that assigns positions along a single dimension (either x or * y) according to a specified data field. By default, the range of values * along the axis is automatically determined by the minimum and maximum * values of the data field. The range bounds can be manually set using the * {@link #setRangeModel(ValuedRangeModel)} method. Also, the set of items * processed by this layout can be filtered by providing a filtering * predicate (@link #setFilter(Predicate)). * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class AxisLayout extends Layout { private String m_field; private int m_scale = Constants.LINEAR_SCALE; private int m_axis = Constants.X_AXIS; private int m_type = Constants.UNKNOWN; // visible region of the layout (in item coordinates) // if false, the table will be consulted private boolean m_modelSet = false; private ValuedRangeModel m_model = null; private Predicate m_filter = null; // screen coordinate range private double m_min; private double m_range; // value range / distribution private double[] m_dist = new double[2]; /** * Create a new AxisLayout. Defaults to using the x-axis. * @param group the data group to layout * @param field the data field upon which to base the layout */ public AxisLayout(String group, String field) { super(group); m_field = field; } /** * Create a new AxisLayout. * @param group the data group to layout * @param field the data field upon which to base the layout * @param axis the axis type, either {@link prefuse.Constants#X_AXIS} * or {@link prefuse.Constants#Y_AXIS}. */ public AxisLayout(String group, String field, int axis) { this(group, field); setAxis(axis); } /** * Create a new AxisLayout. * @param group the data group to layout * @param field the data field upon which to base the layout * @param axis the axis type, either {@link prefuse.Constants#X_AXIS} * or {@link prefuse.Constants#Y_AXIS}. * @param filter an optional predicate filter for limiting which items * to layout. */ public AxisLayout(String group, String field, int axis, Predicate filter) { this(group, field, axis); setFilter(filter); } // ------------------------------------------------------------------------ /** * Set the data field used by this axis layout action. The values of the * data field will determine the position of items along the axis. Note * that this method does not affect the other parameters of this action. In * particular, clients that have provided a custom range model for * setting the axis range may need to appropriately update the model * setting for use with the new data field setting. * @param field the name of the data field that determines the layout */ public void setDataField(String field) { m_field = field; if ( !m_modelSet ) m_model = null; } /** * Get the data field used by this axis layout action. The values of the * data field determine the position of items along the axis. * @return the name of the data field that determines the layout */ public String getDataField() { return m_field; } /** * Set the range model determing the span of the axis. This model controls * the minimum and maximum values of the layout, as provided by the * {@link prefuse.util.ui.ValuedRangeModel#getLowValue()} and * {@link prefuse.util.ui.ValuedRangeModel#getHighValue()} methods. * @param model the range model for the axis. */ public void setRangeModel(ValuedRangeModel model) { m_model = model; m_modelSet = (model != null); } /** * Get the range model determing the span of the axis. This model controls * the minimum and maximum values of the layout, as provided by the * {@link prefuse.util.ui.ValuedRangeModel#getLowValue()} and * {@link prefuse.util.ui.ValuedRangeModel#getHighValue()} methods. * @return the range model for the axis. */ public ValuedRangeModel getRangeModel() { return m_model; } /** * Set a predicate filter to limit which items are considered for layout. * Only items for which the predicate returns a true value are included * in the layout computation. * @param filter the predicate filter to use. If null, no filtering * will be performed. */ public void setFilter(Predicate filter) { m_filter = filter; } /** * Get the predicate filter to limit which items are considered for layout. * Only items for which the predicate returns a true value are included * in the layout computation. * @return the predicate filter used by this layout. If null, no filtering * is performed. */ public Predicate getFilter() { return m_filter; } // ------------------------------------------------------------------------ /** * Returns the scale type used for the axis. This setting only applies * for numerical data types (i.e., when axis values are from a * <code>NumberValuedRange</code>). * @return the scale type. One of * {@link prefuse.Constants#LINEAR_SCALE}, * {@link prefuse.Constants#SQRT_SCALE}, or * {@link Constants#LOG_SCALE}. */ public int getScale() { return m_scale; } /** * Sets the scale type used for the axis. This setting only applies * for numerical data types (i.e., when axis values are from a * <code>NumberValuedRange</code>). * @param scale the scale type. One of * {@link prefuse.Constants#LINEAR_SCALE}, * {@link prefuse.Constants#SQRT_SCALE}, or * {@link Constants#LOG_SCALE}. */ public void setScale(int scale) { if ( scale < 0 || scale >= Constants.SCALE_COUNT ) throw new IllegalArgumentException( "Unrecognized scale value: "+scale); m_scale = scale; } /** * Return the axis type of this layout, either * {@link prefuse.Constants#X_AXIS} or {@link prefuse.Constants#Y_AXIS}. * @return the axis type of this layout. */ public int getAxis() { return m_axis; } /** * Set the axis type of this layout. * @param axis the axis type to use for this layout, either * {@link prefuse.Constants#X_AXIS} or {@link prefuse.Constants#Y_AXIS}. */ public void setAxis(int axis) { if ( axis < 0 || axis >= Constants.AXIS_COUNT ) throw new IllegalArgumentException( "Unrecognized axis value: "+axis); m_axis = axis; } /** * Return the data type used by this layout. This value is one of * {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL}, * {@link prefuse.Constants#NUMERICAL}, or * {@link prefuse.Constants#UNKNOWN}. * @return the data type used by this layout */ public int getDataType() { return m_type; } /** * Set the data type used by this layout. * @param type the data type used by this layout, one of * {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL}, * {@link prefuse.Constants#NUMERICAL}, or * {@link prefuse.Constants#UNKNOWN}. */ public void setDataType(int type) { if ( type < 0 || type >= Constants.DATATYPE_COUNT ) throw new IllegalArgumentException( "Unrecognized data type value: "+type); m_type = type; } // ------------------------------------------------------------------------ /** * @see prefuse.action.Action#run(double) */ public void run(double frac) { TupleSet ts = m_vis.getGroup(m_group); setMinMax(); switch ( getDataType(ts) ) { case Constants.NUMERICAL: numericalLayout(ts); break; default: ordinalLayout(ts); } } /** * Retrieve the data type. */ protected int getDataType(TupleSet ts) { if ( m_type == Constants.UNKNOWN ) { boolean numbers = true; if ( ts instanceof Table ) { numbers = ((Table)ts).canGetDouble(m_field); } else { for ( Iterator it = ts.tuples(); it.hasNext(); ) { if ( !((Tuple)it.next()).canGetDouble(m_field) ) { numbers = false; break; } } } if ( numbers ) { return Constants.NUMERICAL; } else { return Constants.ORDINAL; } } else { return m_type; } } /** * Set the minimum and maximum pixel values. */ private void setMinMax() { Rectangle2D b = getLayoutBounds(); if ( m_axis == Constants.X_AXIS ) { m_min = b.getMinX(); m_range = b.getMaxX() - m_min; } else { m_min = b.getMaxY(); m_range = b.getMinY() - m_min; } } /** * Set the layout position of an item. */ protected void set(VisualItem item, double frac) { double xOrY = m_min + frac*m_range; if ( m_axis == Constants.X_AXIS ) { setX(item, null, xOrY); } else { setY(item, null, xOrY); } } /** * Compute a quantitative axis layout. */ protected void numericalLayout(TupleSet ts) { if ( !m_modelSet ) { m_dist[0] = DataLib.min(ts, m_field).getDouble(m_field); m_dist[1] = DataLib.max(ts, m_field).getDouble(m_field); double lo = m_dist[0], hi = m_dist[1]; if ( m_model == null ) { m_model = new NumberRangeModel(lo, hi, lo, hi); } else { ((NumberRangeModel)m_model).setValueRange(lo, hi, lo, hi); } } else { m_dist[0] = ((Number)m_model.getLowValue()).doubleValue(); m_dist[1] = ((Number)m_model.getHighValue()).doubleValue(); } Iterator iter = m_vis.items(m_group, m_filter); while ( iter.hasNext() ) { VisualItem item = (VisualItem)iter.next(); double v = item.getDouble(m_field); double f = MathLib.interp(m_scale, v, m_dist); set(item, f); } } /** * Compute an ordinal axis layout. */ protected void ordinalLayout(TupleSet ts) { if ( !m_modelSet) { Object[] array = DataLib.ordinalArray(ts, m_field); if ( m_model == null ) { m_model = new ObjectRangeModel(array); } else { ((ObjectRangeModel)m_model).setValueRange(array); } } ObjectRangeModel model = (ObjectRangeModel)m_model; int start = model.getValue(); int end = start + model.getExtent(); double total = (double)(end-start); Iterator iter = m_vis.items(m_group, m_filter); while ( iter.hasNext() ) { VisualItem item = (VisualItem)iter.next(); int order = model.getIndex(item.get(m_field)) - start; set(item, order/total); } } } // end of class AxisLayout