package prefuse.data.column;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import prefuse.data.Table;
import prefuse.data.event.ColumnListener;
import prefuse.data.util.Index;
import prefuse.util.DataLib;
import prefuse.util.TypeLib;
import prefuse.util.collections.DefaultLiteralComparator;
/**
* ColumnMetadata stores computed metadata and statistics for a singe column
* instance. They are created automatically by Table instances and are
* retrieved using the {@link prefuse.data.Table#getMetadata(String)} method.
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class ColumnMetadata implements ColumnListener {
// TODO consider refactor. is non-dynamic mode needed? pass Column reference in?
private Table m_table;
private String m_field;
private boolean m_dynamic;
private boolean m_init;
private Comparator m_cmp;
private Object m_default;
private int m_min;
private int m_max;
private int m_median;
private int m_unique;
private Double m_mean;
private Double m_stdev;
private Double m_sum;
private Object[] m_ordinalA;
private Map m_ordinalM;
// ------------------------------------------------------------------------
/**
* Creates a new ColumnMetadata instance.
* @param table the backing table
* @param column the name of the column to store metadata for
*/
public ColumnMetadata(Table table, String column) {
this(table, column, DefaultLiteralComparator.getInstance(), true);
}
/**
* Creates a new ColumnMetadata instance.
* @param table the backing table
* @param column the name of the column to store metadata for
* @param cmp a Comparator that determines the default sort order for
* values in the column
* @param dynamic indicates if this ColumnMetadata should react to
* changes in the underlying table values. If true, computed values
* stored in this metadata object will be invalidated when updates to
* the column data occur.
*/
public ColumnMetadata(Table table, String column,
Comparator cmp, boolean dynamic)
{
m_table = table;
m_field = column;
m_cmp = cmp;
m_dynamic = dynamic;
}
/**
* Dispose of this metadata, freeing any resources and unregistering any
* listeners.
*/
public void dispose() {
m_table.getColumn(m_field).removeColumnListener(this);
}
// ------------------------------------------------------------------------
private void clearCachedValues() {
m_min = -1;
m_max = -1;
m_median = -1;
m_unique = -1;
m_mean = null;
m_stdev = null;
m_sum = null;
m_ordinalA = null;
m_ordinalM = null;
}
/**
* Re-calculates all the metadata and statistics maintained by this object.
*/
public void calculateValues() {
clearCachedValues();
boolean dyn = m_dynamic;
m_dynamic = true;
getMinimumRow();
getMaximumRow();
getMedianRow();
getUniqueCount();
if ( TypeLib.isNumericType(m_table.getColumnType(m_field)) ) {
getMean();
getDeviation();
getSum();
}
getOrdinalArray();
getOrdinalMap();
m_init = true;
m_dynamic = dyn;
}
private void accessCheck() {
if ( m_init ) return;
if ( m_dynamic ) {
clearCachedValues();
m_table.getColumn(m_field).addColumnListener(this);
} else {
calculateValues();
}
m_init = true;
}
// ------------------------------------------------------------------------
/**
* Returns the comparator used to determine column values' sort order.
* @return the Comparator
*/
public Comparator getComparator() {
return m_cmp;
}
/**
* Sets the comparator used to detemine column values' sort order.
* @param c the Comparator to use
*/
public void setComparator(Comparator c) {
m_cmp = c;
clearCachedValues();
}
/**
* Get the columns' default value.
* @return the column's default value
*/
public Object getDefaultValue() {
return m_default;
}
/**
* Get the row index of the minimum column value. If there are multiple
* minima, only one is returned.
* @return the row index of the minimum column value.
*/
public int getMinimumRow() {
accessCheck();
if ( m_min == -1 && m_dynamic ) {
Index idx = m_table.getIndex(m_field);
if ( idx != null ) {
m_min = idx.minimum();
} else {
m_min = DataLib.min(m_table.tuples(), m_field, m_cmp).getRow();
}
}
return m_min;
}
/**
* Get the row index of the maximum column value. If there are multiple
* maxima, only one is returned.
* @return the row index of the maximum column value.
*/
public int getMaximumRow() {
accessCheck();
if ( m_max == -1 && m_dynamic ) {
Index idx = m_table.getIndex(m_field);
if ( idx != null ) {
m_max = idx.maximum();
} else {
m_max = DataLib.max(m_table.tuples(), m_field, m_cmp).getRow();
}
}
return m_max;
}
/**
* Get the row index of the median column value.
* @return the row index of the median column value
*/
public int getMedianRow() {
accessCheck();
if ( m_median == -1 && m_dynamic ) {
Index idx = m_table.getIndex(m_field);
if ( idx != null ) {
m_max = idx.median();
} else {
m_median = DataLib.median(
m_table.tuples(), m_field, m_cmp).getRow();
}
}
return m_median;
}
/**
* Get the number of unique values in the column.
* @return the number of unique values in the column
*/
public int getUniqueCount() {
accessCheck();
if ( m_unique == -1 && m_dynamic ) {
Index idx = m_table.getIndex(m_field);
if ( idx != null ) {
m_unique = idx.uniqueCount();
} else {
m_unique = DataLib.uniqueCount(m_table.tuples(), m_field);
}
}
return m_unique;
}
/**
* Get the mean value of numeric values in the column. If this column
* does not contain numeric values, this method will result in an
* exception being thrown.
* @return the mean of numeric values in the column
*/
public double getMean() {
accessCheck();
if ( m_mean == null && m_dynamic ) {
m_mean = new Double(DataLib.mean(m_table.tuples(), m_field));
}
return m_mean.doubleValue();
}
/**
* Get the standard deviation of numeric values in the column. If this column
* does not contain numeric values, this method will result in an
* exception being thrown.
* @return the standard deviation of numeric values in the column
*/
public double getDeviation() {
accessCheck();
if ( m_stdev == null && m_dynamic ) {
m_stdev = new Double(
DataLib.deviation(m_table.tuples(), m_field, getMean()));
}
return m_stdev.doubleValue();
}
/**
* Get the sum of numeric values in the column. If this column
* does not contain numeric values, this method will result in an
* exception being thrown.
* @return the sum of numeric values in the column
*/
public double getSum() {
accessCheck();
if ( m_sum == null && m_dynamic ) {
m_sum = new Double(DataLib.sum(m_table.tuples(), m_field));
}
return m_sum.doubleValue();
}
/**
* Get an array of all unique column values, in sorted order.
* @return an array of all unique column values, in sorted order.
*/
public Object[] getOrdinalArray() {
accessCheck();
if ( m_ordinalA == null && m_dynamic ) {
m_ordinalA = DataLib.ordinalArray(m_table.tuples(),m_field,m_cmp);
}
return m_ordinalA;
}
/**
* Get a map between all unique column values and their integer index
* in the sort order of those values. For example, the minimum value
* maps to 0, the next greater value to 1, etc.
* @return a map between all unique column values and their integer index
* in the values' sort order
*/
public Map getOrdinalMap() {
accessCheck();
if ( m_ordinalM == null && m_dynamic ) {
Object[] a = getOrdinalArray();
m_ordinalM = new HashMap();
for ( int i=0; i<a.length; ++i )
m_ordinalM.put(a[i], new Integer(i));
//m_ordinalM = DataLib.ordinalMap(m_table.tuples(), m_field, m_cmp);
}
return m_ordinalM;
}
// ------------------------------------------------------------------------
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, int, int)
*/
public void columnChanged(Column src, int type, int start, int end) {
clearCachedValues();
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, boolean)
*/
public void columnChanged(Column src, int idx, boolean prev) {
columnChanged(src, 0, idx, idx);
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, double)
*/
public void columnChanged(Column src, int idx, double prev) {
columnChanged(src, 0, idx, idx);
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, float)
*/
public void columnChanged(Column src, int idx, float prev) {
columnChanged(src, 0, idx, idx);
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, int)
*/
public void columnChanged(Column src, int idx, int prev) {
columnChanged(src, 0, idx, idx);
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, long)
*/
public void columnChanged(Column src, int idx, long prev) {
columnChanged(src, 0, idx, idx);
}
/**
* @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, java.lang.Object)
*/
public void columnChanged(Column src, int idx, Object prev) {
columnChanged(src, 0, idx, idx);
}
} // end of class ColumnMetadata