package prefuse.action.assignment;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import prefuse.Constants;
import prefuse.data.tuple.TupleSet;
import prefuse.util.ColorLib;
import prefuse.util.ColorMap;
import prefuse.util.DataLib;
import prefuse.util.MathLib;
import prefuse.visual.VisualItem;
/**
* <p>
* Assignment Action that assigns color values for a group of items based upon
* a data field. The type of color encoding used is dependent upon the
* reported data type. Nominal (categorical) data is encoded using a different
* hue for each unique data value. Ordinal (ordered) and Numerical
* (quantitative) data is shown using a grayscale color ramp. In all cases,
* the default color palette used by this Action can be replaced with a
* client-specified palette provided to the DataColorAction constructor.
* </p>
*
* <p>
* The color spectra for numerical data is continuous by default, but can also
* be binned into a few discrete steps (see {@link #setBinCount(int)}).
* Quantitative data can also be colored on different numerical scales. The
* default scale is a linear scale (specified by
* {@link Constants#LINEAR_SCALE}), but logarithmic and square root scales can
* be used (specified by {@link Constants#LOG_SCALE} and
* {@link Constants#SQRT_SCALE} respectively. Finally, the scale can be broken
* into quantiles, reflecting the statistical distribution of the values rather
* than just the total data value range, using the
* {@link Constants#QUANTILE_SCALE} value. For the quantile scale to work, you
* also need to specify the number of bins to use (see
* {@link #setBinCount(int)}). This value will determine the number of
* quantiles that the data should be divided into.
* </p>
*
* </p>
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class DataColorAction extends ColorAction {
private String m_dataField;
private int m_type;
private int m_scale = Constants.LINEAR_SCALE;
private int m_tempScale;
private double[] m_dist;
private int m_bins = Constants.CONTINUOUS;
private Map m_omap;
private Object[] m_olist;
private ColorMap m_cmap = new ColorMap(null,0,1);
private int[] m_palette;
/**
* Create a new DataColorAction
* @param group the data group to process
* @param dataField the data field to base size assignments on
* @param dataType the data type to use for the data field. One of
* {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
* or {@link prefuse.Constants#NUMERICAL}, for whether the data field
* represents categories, an ordered sequence, or numerical values.
* @param colorField the color field to assign
*/
public DataColorAction(String group, String dataField,
int dataType, String colorField)
{
super(group, colorField);
setDataType(dataType);
setDataField(dataField);
}
/**
* Create a new DataColorAction
* @param group the data group to process
* @param dataField the data field to base size assignments on
* @param dataType the data type to use for the data field. One of
* {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
* or {@link prefuse.Constants#NUMERICAL}, for whether the data field
* represents categories, an ordered sequence, or numerical values.
* @param colorField the color field to assign
* @param palette the color palette to use. See
* {@link prefuse.util.ColorLib} for color palette generators.
*/
public DataColorAction(String group, String dataField,
int dataType, String colorField, int[] palette)
{
super(group, colorField);
setDataType(dataType);
setDataField(dataField);
m_palette = palette;
}
// ------------------------------------------------------------------------
/**
* Returns the data field used to encode size values.
* @return the data field that is mapped to size values
*/
public String getDataField() {
return m_dataField;
}
/**
* Set the data field used to encode size values.
* @param field the data field to map to size values
*/
public void setDataField(String field) {
m_dataField = field;
}
/**
* Return the data type used by this action. This value is one of
* {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
* or {@link prefuse.Constants#NUMERICAL}.
* @return the data type used by this action
*/
public int getDataType() {
return m_type;
}
/**
* Set the data type used by this action.
* @param type the data type used by this action, one of
* {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
* or {@link prefuse.Constants#NUMERICAL}.
*/
public void setDataType(int type) {
if ( type < 0 || type >= Constants.DATATYPE_COUNT )
throw new IllegalArgumentException(
"Unrecognized data type: "+type);
m_type = type;
}
/**
* Returns the scale type used for encoding color values from the data.
* This value is only used for {@link prefuse.Constants#NUMERICAL}
* data.
* @return the scale type. One of
* {@link prefuse.Constants#LINEAR_SCALE},
* {@link prefuse.Constants#LOG_SCALE},
* {@link prefuse.Constants#SQRT_SCALE},
* {@link prefuse.Constants#QUANTILE_SCALE}.
*/
public int getScale() {
return m_scale;
}
/**
* Set the scale (linear, square root, or log) to use for encoding color
* values from the data. This value is only used for
* {@link prefuse.Constants#NUMERICAL} data.
* @param scale the scale type to use. This value should be one of
* {@link prefuse.Constants#LINEAR_SCALE},
* {@link prefuse.Constants#SQRT_SCALE},
* {@link prefuse.Constants#LOG_SCALE},
* {@link prefuse.Constants#QUANTILE_SCALE}.
* If {@link prefuse.Constants#QUANTILE_SCALE} is used, the number of
* bins to use must also be specified to a value greater than zero using
* the {@link #setBinCount(int)} method.
*/
public void setScale(int scale) {
if ( scale < 0 || scale >= Constants.SCALE_COUNT )
throw new IllegalArgumentException(
"Unrecognized scale value: "+scale);
m_scale = scale;
}
/**
* Returns the number of "bins" or discrete steps of color. This value
* is only used for numerical data.
* @return the number of bins.
*/
public int getBinCount() {
return m_bins;
}
/**
* Sets the number of "bins" or or discrete steps of color. This value
* is only used for numerical data.
* @param count the number of bins to set. The value
* {@link Constants#CONTINUOUS} indicates not to use any binning. If the
* scale type set using the {@link #setScale(int)} method is
* {@link Constants#QUANTILE_SCALE}, the bin count <strong>must</strong>
* be greater than zero.
*/
public void setBinCount(int count) {
if ( m_scale == Constants.QUANTILE_SCALE && count <= 0 ) {
throw new IllegalArgumentException(
"The quantile scale can not be used without binning. " +
"Use a bin value greater than zero.");
}
m_bins = count;
}
/**
* This operation is not supported by the DataColorAction type.
* Calling this method will result in a thrown exception.
* @see prefuse.action.assignment.ColorAction#setDefaultColor(int)
* @throws UnsupportedOperationException
*/
public void setDefaultColor(int color) {
throw new UnsupportedOperationException();
}
/**
* Manually sets the ordered list of values to use for color assignment.
* Normally, this ordering is computed using the methods of the
* {@link prefuse.util.DataLib} class. This method allows you to set your
* own custom ordering. This ordering corresponds to the ordering of
* colors in this action's color palette. If the provided array of values
* is missing a value contained within the data, an exception will result
* during execution of this action.
* @param values the ordered list of values. If this array is missing a
* value contained within data processed by this action, an exception
* will be thrown when this action is run.
*/
public void setOrdinalMap(Object[] values) {
m_olist = values;
m_omap = new HashMap();
for ( int i=0; i<values.length; ++i ) {
m_omap.put(values[i], new Integer(i));
}
}
// ------------------------------------------------------------------------
/**
* Set up the state of this encoding Action.
* @see prefuse.action.EncoderAction#setup()
*/
protected void setup() {
int size = 64;
int[] palette = m_palette;
// switch up scale if necessary
m_tempScale = m_scale;
if ( m_scale == Constants.QUANTILE_SCALE && m_bins <= 0 ) {
Logger.getLogger(getClass().getName()).warning(
"Can't use quantile scale with no binning. " +
"Defaulting to linear scale. Set the bin value " +
"greater than zero to use a quantile scale.");
m_scale = Constants.LINEAR_SCALE;
}
// compute distribution and color map
switch ( m_type ) {
case Constants.NOMINAL:
case Constants.ORDINAL:
m_dist = getDistribution();
size = m_omap.size();
palette = (m_palette!=null ? m_palette : createPalette(size));
m_cmap.setColorPalette(palette);
m_cmap.setMinValue(m_dist[0]);
m_cmap.setMaxValue(m_dist[1]);
return;
case Constants.NUMERICAL:
m_dist = getDistribution();
size = m_bins > 0 ? m_bins : size;
palette = (m_palette!=null ? m_palette : createPalette(size));
m_cmap.setColorPalette(palette);
m_cmap.setMinValue(0.0);
m_cmap.setMaxValue(1.0);
return;
}
}
protected void finish() {
// reset scale in case it needed to be changed due to errors
m_scale = m_tempScale;
}
/**
* Computes the distribution (either min/max or quantile values) used to
* help assign colors to data values.
*/
protected double[] getDistribution() {
TupleSet ts = m_vis.getGroup(m_group);
if ( m_type == Constants.NUMERICAL ) {
m_omap = null;
if ( m_scale == Constants.QUANTILE_SCALE && m_bins > 0 ) {
double[] values =
DataLib.toDoubleArray(ts.tuples(), m_dataField);
return MathLib.quantiles(m_bins, values);
} else {
double[] dist = new double[2];
dist[0] = DataLib.min(ts, m_dataField).getDouble(m_dataField);
dist[1] = DataLib.max(ts, m_dataField).getDouble(m_dataField);
return dist;
}
} else {
if ( m_olist == null )
m_omap = DataLib.ordinalMap(ts, m_dataField);
return new double[] { 0, m_omap.size()-1 };
}
}
/**
* Create a color palette of the requested type and size.
*/
protected int[] createPalette(int size) {
switch ( m_type ) {
case Constants.NOMINAL:
return ColorLib.getCategoryPalette(size);
case Constants.NUMERICAL:
case Constants.ORDINAL:
default:
return ColorLib.getGrayscalePalette(size);
}
}
/**
* @see prefuse.action.assignment.ColorAction#getColor(prefuse.visual.VisualItem)
*/
public int getColor(VisualItem item) {
// check for any cascaded rules first
Object o = lookup(item);
if ( o != null ) {
if ( o instanceof ColorAction ) {
return ((ColorAction)o).getColor(item);
} else if ( o instanceof Integer ) {
return ((Integer)o).intValue();
} else {
Logger.getLogger(this.getClass().getName())
.warning("Unrecognized Object from predicate chain.");
}
}
// otherwise perform data-driven assignment
switch ( m_type ) {
case Constants.NUMERICAL:
double v = item.getDouble(m_dataField);
double f = MathLib.interp(m_scale, v, m_dist);
return m_cmap.getColor(f);
default:
Integer idx = (Integer)m_omap.get(item.get(m_dataField));
return m_cmap.getColor(idx.doubleValue());
}
}
} // end of class DataColorAction