View Javadoc

    1   package baseCode.gui;
    2
    3   import java.awt.Color;
    4   import java.io.IOException;
    5
    6   import baseCode.dataStructure.matrix.AbstractNamedDoubleMatrix;
    7   import baseCode.dataStructure.matrix.DenseDoubleMatrix2DNamed;
    8   import baseCode.io.reader.DoubleMatrixReader;
    9   import baseCode.math.DescriptiveWithMissing;
    10  import baseCode.math.MatrixStats;
    11  import cern.colt.list.DoubleArrayList;
    12
    13  /***
    14   * <p>
    15   * Title: ColorMatrix
    16   * </p>
    17   * <p>
    18   * Description: Creates a color matrix from a matrix of doubles
    19   * </p>
    20   * <p>
    21   * Copyright (c) 2004
    22   * </p>
    23   * <p>
    24   * Institution:: Columbia University
    25   * </p>
    26   *
    27   * @author Will Braynen
    28   * @version $Id: ColorMatrix.java,v 1.42 2004/09/20 22:19:02 pavlidis Exp $
    29   */
    30  public class ColorMatrix implements Cloneable {
    31
    32     // data fields
    33
    34     /***
    35      * Min and max values to display, which might not be the actual min and max values in the matrix. For instance, we
    36      * might want to clip values, or show a bigger color range for equal comparison with other rows or matrices.
    37      */
    38     protected double m_displayMin;
    39     protected double m_displayMax;
    40
    41     protected double m_min;
    42     protected double m_max;
    43
    44     protected Color[][] m_colors;
    45     protected Color m_missingColor = Color.lightGray;
    46     protected Color[] m_colorMap = ColorMap.BLACKBODY_COLORMAP;
    47
    48     protected AbstractNamedDoubleMatrix m_matrix;
    49     protected DoubleMatrixReader m_matrixReader;
    50
    51     protected int m_totalRows, m_totalColumns;
    52
    53     /*** to be able to sort the rows by an arbitrary key */
    54     protected int m_rowKeys[];
    55
    56     /***
    57      * @param filename either an absolute path, or if providing a relative path (e.g. data.txt), then keep in mind that
    58      *        it will be relative to the java interpreter, not the class (not my fault -- that's how java treats relative
    59      *        paths)
    60      * @throws IOException
    61      */
    62     public ColorMatrix( String filename ) throws IOException {
    63        loadMatrixFromFile( filename );
    64     }
    65
    66     public ColorMatrix( DenseDoubleMatrix2DNamed matrix ) {
    67        init( matrix );
    68     }
    69
    70     /***
    71      * @param filename data filename
    72      * @param colorMap the simplest color map is one with just two colors: { minColor, maxColor }
    73      * @param missingColor values missing from the matrix or non-numeric entries will be displayed using this color
    74      * @throws IOException
    75      */
    76     public ColorMatrix( String filename, Color[] colorMap, Color missingColor )
    77           throws IOException {
    78
    79        m_missingColor = missingColor;
    80        m_colorMap = colorMap;
    81        loadMatrixFromFile( filename );
    82     }
    83
    84     /***
    85      * @param matrix the matrix
    86      * @param colorMap the simplest color map is one with just two colors: { minColor, maxColor }
    87      * @param missingColor values missing from the matrix or non-numeric entries will be displayed using this color
    88      */
    89     public ColorMatrix( AbstractNamedDoubleMatrix matrix, Color[] colorMap,
    90           Color missingColor ) {
    91
    92        m_missingColor = missingColor;
    93        m_colorMap = colorMap;
    94        init( matrix );
    95     }
    96
    97     public int getRowCount() {
    98        return m_totalRows;
    99     }
    100
    101    public int getColumnCount() {
    102       return m_totalColumns;
    103    }
    104
    105    protected int getTrueRowIndex( int row ) {
    106       return m_rowKeys[row];
    107    }
    108
    109    public double getValue( int row, int column )
    110          throws ArrayIndexOutOfBoundsException {
    111
    112       if ( row >= m_totalRows )
    113             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    114                   + m_totalRows
    115                   + " rows, so the maximum possible row index is "
    116                   + ( m_totalRows - 1 ) + ". Row index " + row
    117                   + " is too high." );
    118       if ( column >= m_totalColumns )
    119             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    120                   + m_totalColumns
    121                   + " columns, so the maximum possible column index is "
    122                   + ( m_totalColumns - 1 ) + ". Column index " + column
    123                   + " is too high." );
    124
    125       row = getTrueRowIndex( row );
    126       return m_matrix.get( row, column );
    127    }
    128
    129    public double[] getRow( int row ) throws ArrayIndexOutOfBoundsException {
    130
    131       if ( row >= m_totalRows )
    132             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    133                   + m_totalRows
    134                   + " rows, so the maximum possible row index is "
    135                   + ( m_totalRows - 1 ) + ". Row index " + row
    136                   + " is too high." );
    137
    138       row = getTrueRowIndex( row );
    139       return m_matrix.getRow( row );
    140    }
    141
    142    public double[] getRowByName( String rowName ) {
    143       return m_matrix.getRowByName( rowName );
    144    }
    145
    146    public Color getColor( int row, int column )
    147          throws ArrayIndexOutOfBoundsException {
    148
    149       if ( row >= m_totalRows )
    150             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    151                   + m_totalRows
    152                   + " rows, so the maximum possible row index is "
    153                   + ( m_totalRows - 1 ) + ". Row index " + row
    154                   + " is too high." );
    155       if ( column >= m_totalColumns )
    156             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    157                   + m_totalColumns
    158                   + " columns, so the maximum possible column index is "
    159                   + ( m_totalColumns - 1 ) + ". Column index " + column
    160                   + " is too high." );
    161
    162       row = getTrueRowIndex( row );
    163       return m_colors[row][column];
    164    }
    165
    166    public void setColor( int row, int column, Color newColor )
    167          throws ArrayIndexOutOfBoundsException {
    168
    169       if ( row >= m_totalRows )
    170             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    171                   + m_totalRows
    172                   + " rows, so the maximum possible row index is "
    173                   + ( m_totalRows - 1 ) + ". Row index " + row
    174                   + " is too high." );
    175       if ( column >= m_totalColumns )
    176             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    177                   + m_totalColumns
    178                   + " columns, so the maximum possible column index is "
    179                   + ( m_totalColumns - 1 ) + ". Column index " + column
    180                   + " is too high." );
    181
    182       row = getTrueRowIndex( row );
    183       m_colors[row][column] = newColor;
    184    }
    185
    186    public String getRowName( int row ) throws ArrayIndexOutOfBoundsException {
    187
    188       if ( row >= m_totalRows )
    189             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    190                   + m_totalRows
    191                   + " rows, so the maximum possible row index is "
    192                   + ( m_totalRows - 1 ) + ". Row index " + row
    193                   + " is too high." );
    194
    195       row = getTrueRowIndex( row );
    196       return m_matrix.getRowName( row );
    197    }
    198
    199    public String getColumnName( int column )
    200          throws ArrayIndexOutOfBoundsException {
    201
    202       if ( column >= m_totalColumns )
    203             throw new ArrayIndexOutOfBoundsException( "The matrix has only "
    204                   + m_totalColumns
    205                   + " columns, so the maximum possible column index is "
    206                   + ( m_totalColumns - 1 ) + ". Column index " + column
    207                   + " is too high." );
    208
    209       return m_matrix.getColName( column );
    210    }
    211
    212    public String[] getRowNames() {
    213       String[] rowNames = new String[m_totalRows];
    214       for ( int i = 0; i < m_totalRows; i++ ) {
    215          int row = getTrueRowIndex( i );
    216          rowNames[i] = getRowName( row );
    217       }
    218       return rowNames;
    219    }
    220
    221    public String[] getColumnNames() {
    222       String[] columnNames = new String[m_totalColumns];
    223       for ( int i = 0; i < m_totalColumns; i++ ) {
    224          columnNames[i] = getColumnName( i );
    225       }
    226       return columnNames;
    227    }
    228
    229    public int getRowIndexByName( String rowName ) {
    230       return m_matrix.getRowIndexByName( rowName );
    231    }
    232
    233    /***
    234     * Changes values in a row, clipping if there are more values than columns.
    235     *
    236     * @param row row whose values we want to change
    237     * @param values new row values
    238     */
    239    protected void setRow( int row, double values[] ) {
    240
    241       row = getTrueRowIndex( row );
    242
    243       // clip if we have more values than columns
    244       int totalValues = Math.min( values.length, m_totalColumns );
    245
    246       for ( int column = 0; column < totalValues; column++ ) {
    247          m_matrix.set( row, column, values[column] );
    248
    249       }
    250    } // end setRow
    251
    252    /***
    253     * To be able to sort the rows by an arbitrary key. Creates <code>m_rowKeys</code> array and initializes it in
    254     * ascending order from 0 to <code>m_totalRows</code> -1, so that by default it matches the physical order of the
    255     * columns: [0,1,2,...,m_totalRows-1]
    256     *
    257     * @return int[]
    258     */
    259    protected int[] createRowKeys() {
    260       m_rowKeys = new int[m_totalRows];
    261       for ( int i = 0; i < m_totalRows; i++ ) {
    262          m_rowKeys[i] = i;
    263       }
    264       return m_rowKeys;
    265    }
    266
    267    public void setRowKeys( int[] rowKeys ) {
    268       m_rowKeys = rowKeys;
    269    }
    270
    271    public void resetRowKeys() {
    272       for ( int i = 0; i < m_totalRows; i++ ) {
    273          m_rowKeys[i] = i;
    274       }
    275    }
    276
    277    public void init( AbstractNamedDoubleMatrix matrix ) {
    278
    279       m_matrix = matrix; // by reference, or should we clone?
    280       m_totalRows = m_matrix.rows();
    281       m_totalColumns = m_matrix.columns();
    282       m_colors = new Color[m_totalRows][m_totalColumns];
    283       createRowKeys();
    284
    285       m_displayMin = m_min = MatrixStats.min( m_matrix );
    286       m_displayMax = m_max = MatrixStats.max( m_matrix );
    287
    288       // map values to colors
    289       mapValuesToColors();
    290    }
    291
    292    /***
    293     * A convenience method for loading data files
    294     *
    295     * @param filename the name of the data file
    296     * @throws IOException
    297     */
    298    public void loadMatrixFromFile( String filename ) throws IOException {
    299
    300       m_matrixReader = new DoubleMatrixReader();
    301       DenseDoubleMatrix2DNamed matrix = ( DenseDoubleMatrix2DNamed ) m_matrixReader
    302             .read( filename );
    303       init( matrix );
    304    }
    305
    306    /***
    307     * Normalizes the elements of a matrix to variance one and mean zero, ignoring missing values todo move this to
    308     * matrixstats or something.
    309     */
    310    public void standardize() {
    311
    312       // normalize the data in each row
    313       for ( int r = 0; r < m_totalRows; r++ ) {
    314          double[] rowValues = getRow( r );
    315          DoubleArrayList doubleArrayList = new cern.colt.list.DoubleArrayList(
    316                rowValues );
    317          DescriptiveWithMissing.standardize( doubleArrayList );
    318          rowValues = doubleArrayList.elements();
    319          setRow( r, rowValues );
    320       }
    321
    322       m_displayMin = -2;
    323       m_displayMax = +2;
    324
    325       mapValuesToColors();
    326
    327    } // end standardize
    328
    329    /***
    330     * @return a DenseDoubleMatrix2DNamed object
    331     */
    332    public AbstractNamedDoubleMatrix getMatrix() {
    333       return m_matrix;
    334    }
    335
    336    //
    337    // Standardized display range
    338    //
    339    public void setDisplayRange( double min, double max ) {
    340
    341       m_displayMin = min;
    342       m_displayMax = max;
    343
    344       mapValuesToColors();
    345    }
    346
    347    public double getDisplayMin() {
    348       return m_displayMin;
    349    }
    350
    351    public double getDisplayMax() {
    352       return m_displayMax;
    353    }
    354
    355    /***
    356     * @param colorMap an array of colors which define the midpoints in the color map; this can be one of the constants
    357     *        defined in the ColorMap class, like ColorMap.REDGREEN_COLORMAP and ColorMap.BLACKBODY_COLORMAP
    358     * @throws IllegalArgumentException if the colorMap array argument contains less than two colors.
    359     */
    360    public void setColorMap( Color[] colorMap ) throws IllegalArgumentException {
    361
    362       if ( colorMap.length < 2 ) {
    363          throw new IllegalArgumentException();
    364       }
    365
    366       m_colorMap = colorMap;
    367       mapValuesToColors();
    368    }
    369
    370    public void mapValuesToColors() {
    371       ColorMap colorMap = new ColorMap( m_colorMap );
    372       double range = m_displayMax - m_displayMin;
    373
    374       if ( 0.0 == range ) {
    375          // This is not an exception, just a warning, so no exception to throw
    376          System.err.println( "Warning: range of values in data is zero." );
    377          range = 1.0; // This avoids getting a step size of zero
    378          // in case all values in the matrix are equal.
    379       }
    380
    381       // zoom factor for stretching or shrinking the range
    382       double zoomFactor = ( colorMap.getPaletteSize() - 1 ) / range;
    383
    384       // map values to colors
    385       for ( int row = 0; row < m_totalRows; row++ ) {
    386          for ( int column = 0; column < m_totalColumns; column++ ) {
    387             double value = getValue( row, column );
    388
    389             if ( Double.isNaN( value ) ) {
    390                // the value is missing
    391                m_colors[row][column] = m_missingColor;
    392             } else {
    393                // clip extreme values (this makes sense for normalized
    394                // values because then for a standard deviation of one and
    395                // mean zero, we set m_minValue = -2 and m_maxValue = 2,
    396                // even though there could be a few extreme values
    397                // outside this range (right?)
    398                if ( value > m_displayMax ) {
    399                   // clip extremely large values
    400                   value = m_displayMax;
    401                } else if ( value < m_displayMin ) {
    402                   // clip extremely small values
    403                   value = m_displayMin;
    404                }
    405
    406                // shift the minimum value to zero
    407                // to the range [0, maxValue + minValue]
    408                value -= m_displayMin;
    409
    410                // stretch or shrink the range to [0, totalColors]
    411                double valueNew = value * zoomFactor;
    412                int i = ( int ) Math.round( valueNew );
    413                m_colors[row][column] = colorMap.getColor( i );
    414             }
    415          }
    416       }
    417    } // end mapValuesToColors
    418
    419    public Object clone() {
    420       // create another double matrix
    421       DenseDoubleMatrix2DNamed matrix = new DenseDoubleMatrix2DNamed(
    422             m_totalRows, m_totalColumns );
    423       // copy the row and column names
    424       for ( int i = 0; i < m_totalRows; i++ ) {
    425          matrix.addRowName( m_matrix.getRowName( i ), i );
    426       }
    427       for ( int i = 0; i < m_totalColumns; i++ ) {
    428          matrix.addColumnName( m_matrix.getColName( i ), i );
    429          // copy the data
    430       }
    431       for ( int r = 0; r < m_totalRows; r++ ) {
    432          for ( int c = 0; c < m_totalColumns; c++ ) {
    433             matrix.set( r, c, m_matrix.get( r, c ) );
    434          }
    435       }
    436
    437       // create another copy of a color matrix (this class)
    438       ColorMatrix clonedColorMatrix = new ColorMatrix( matrix, m_colorMap,
    439             m_missingColor );
    440
    441       int[] rowKeys = ( int[] ) m_rowKeys.clone();
    442       clonedColorMatrix.setRowKeys( rowKeys );
    443
    444       return clonedColorMatrix;
    445
    446    } // end clone
    447
    448 } // end class ColorMatrix