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