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