View Javadoc
1   /*
2    * The baseCode project
3    * 
4    * Copyright (c) 2006-2011 University of British Columbia
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   */
19  package ubic.basecode.graphics;
20  
21  import java.awt.Color;
22  
23  import ubic.basecode.dataStructure.matrix.DenseDoubleMatrix;
24  import ubic.basecode.dataStructure.matrix.DoubleMatrix;
25  import ubic.basecode.io.reader.DoubleMatrixReader;
26  import ubic.basecode.math.Constants;
27  import ubic.basecode.math.DescriptiveWithMissing;
28  import ubic.basecode.math.MatrixStats;
29  import cern.colt.list.DoubleArrayList;
30  
31  /**
32   * Creates a color matrix from a matrix of doubles
33   * 
34   * @author Will Braynen
35   * 
36   */
37  public class ColorMatrix<A, B> implements Cloneable {
38  
39      // data fields
40  
41      public static <R, C> ColorMatrix<R, C> newInstance( DoubleMatrix<R, C> matrix ) {
42          return new ColorMatrix<R, C>( matrix );
43      }
44  
45      protected Color[] colorMap = ColorMap.BLACKBODY_COLORMAP;
46  
47      protected Color[][] colors;
48      protected double displayMax;
49  
50      /**
51       * Min and max values to display, which might not be the actual min and max values in the matrix. For instance, we
52       * might want to clip values, or show a bigger color range for equal comparison with other rows or matrices.
53       */
54      protected double displayMin;
55      protected DoubleMatrixReader m_matrixReader;
56      /** to be able to sort the rows by an arbitrary key */
57      protected int m_rowKeys[];
58  
59      protected int m_totalRows, m_totalColumns;
60      protected double max;
61  
62      protected DoubleMatrix<A, B> maxtrix;
63  
64      protected double min;
65  
66      protected Color missingColor = Color.lightGray;
67  
68      public ColorMatrix( DoubleMatrix<A, B> matrix ) {
69          init( matrix );
70      }
71  
72      /**
73       * @param matrix the matrix
74       * @param colorMap the simplest color map is one with just two colors: { minColor, maxColor }
75       * @param missingColor values missing from the matrix or non-numeric entries will be displayed using this color
76       */
77      public ColorMatrix( DoubleMatrix<A, B> matrix, Color[] colorMap, Color missingColor ) {
78          this.missingColor = missingColor;
79          this.colorMap = colorMap;
80          init( matrix );
81      }
82  
83      @Override
84      public ColorMatrix<A, B> clone() {
85  
86          try {
87              super.clone();
88          } catch ( CloneNotSupportedException e ) {
89          }
90  
91          // create another double matrix
92          DenseDoubleMatrix<A, B> matrix = new DenseDoubleMatrix<A, B>( m_totalRows, m_totalColumns );
93          // copy the row and column names
94          for ( int i = 0; i < m_totalRows; i++ ) {
95              A rowName = maxtrix.getRowName( i );
96              matrix.setRowName( rowName, i );
97          }
98          for ( int i = 0; i < m_totalColumns; i++ ) {
99              B colName = maxtrix.getColName( i );
100             matrix.setColumnName( colName, i );
101             // copy the data
102         }
103         for ( int r = 0; r < m_totalRows; r++ ) {
104             for ( int c = 0; c < m_totalColumns; c++ ) {
105                 matrix.set( r, c, maxtrix.get( r, c ) );
106             }
107         }
108 
109         // create another copy of a color matrix (this class)
110         ColorMatrix<A, B> clonedColorMatrix = new ColorMatrix<A, B>( matrix, colorMap, missingColor );
111 
112         int[] rowKeys = m_rowKeys.clone();
113         clonedColorMatrix.setRowKeys( rowKeys );
114 
115         return clonedColorMatrix;
116 
117     } // end clone
118 
119     public Color getColor( int row, int column ) throws ArrayIndexOutOfBoundsException {
120 
121         rowRangeCheck( row );
122         colRangeCheck( column );
123 
124         return colors[getTrueRowIndex( row )][column];
125     }
126 
127     public Color[] getColorMap() {
128         return colorMap;
129     }
130 
131     public Color[][] getColors() {
132         return colors;
133     }
134 
135     public int getColumnCount() {
136         return m_totalColumns;
137     }
138 
139     public B getColumnName( int column ) throws ArrayIndexOutOfBoundsException {
140 
141         colRangeCheck( column );
142 
143         return maxtrix.getColName( column );
144     }
145 
146     public String[] getColumnNames() {
147         String[] columnNames = new String[m_totalColumns];
148         for ( int i = 0; i < m_totalColumns; i++ ) {
149             columnNames[i] = getColumnName( i ).toString();
150         }
151         return columnNames;
152     }
153 
154     public double getDisplayMax() {
155         return displayMax;
156     }
157 
158     public double getDisplayMin() {
159         return displayMin;
160     }
161 
162     /**
163      * @return a DenseDoubleMatrix2DNamed object
164      */
165     public DoubleMatrix<A, B> getMatrix() {
166         return maxtrix;
167     }
168 
169     public double getMax() {
170         return max;
171     }
172 
173     public DoubleMatrix<A, B> getMaxtrix() {
174         return maxtrix;
175     }
176 
177     public double getMin() {
178         return min;
179     }
180 
181     public Color getMissingColor() {
182         return missingColor;
183     }
184 
185     public double[] getRow( int row ) throws ArrayIndexOutOfBoundsException {
186 
187         rowRangeCheck( row );
188 
189         return maxtrix.getRow( getTrueRowIndex( row ) );
190     }
191 
192     public double[] getRowByName( A rowName ) {
193         return maxtrix.getRowByName( rowName );
194     }
195 
196     public int getRowCount() {
197         return m_totalRows;
198     }
199 
200     public int getRowIndexByName( A rowName ) {
201         return maxtrix.getRowIndexByName( rowName );
202     }
203 
204     public Object getRowName( int row ) throws ArrayIndexOutOfBoundsException {
205 
206         rowRangeCheck( row );
207         return maxtrix.getRowName( getTrueRowIndex( row ) );
208     }
209 
210     public String[] getRowNames() {
211         String[] rowNames = new String[m_totalRows];
212         for ( int i = 0; i < m_totalRows; i++ ) {
213             int row = getTrueRowIndex( i );
214             Object rowName = getRowName( row );
215             String rowNameString = rowName.toString();
216             rowNames[i] = rowNameString;
217         }
218         return rowNames;
219     }
220 
221     public double getValue( int row, int column ) throws ArrayIndexOutOfBoundsException {
222 
223         rowRangeCheck( row );
224         colRangeCheck( column );
225 
226         return maxtrix.get( getTrueRowIndex( row ), column );
227     }
228 
229     public void mapValuesToColors() {
230         ColorMap#ColorMap">ColorMap colorMapO = new ColorMap( colorMap );
231         double range = displayMax - displayMin;
232 
233         if ( range < Constants.SMALL ) {
234             range = Constants.SMALL;
235         }
236 
237         // zoom factor for stretching or shrinking the range
238         double zoomFactor = ( colorMapO.getPaletteSize() - 1.0 ) / range;
239 
240         // map values to colors
241         for ( int row = 0; row < m_totalRows; row++ ) {
242             for ( int column = 0; column < m_totalColumns; column++ ) {
243                 double value = getValue( row, column );
244 
245                 if ( Double.isNaN( value ) ) {
246                     // the value is missing
247                     colors[row][column] = missingColor;
248                 } else {
249                     // clip extreme values
250                     if ( value > displayMax ) {
251                         value = displayMax;
252                     } else if ( value < displayMin ) {
253                         value = displayMin;
254                     }
255 
256                     // shift the minimum value to zero
257                     // to the range [0, maxValue + minValue]
258                     value -= displayMin;
259 
260                     // stretch or shrink the range to [0, totalColors]
261                     double valueNew = value * zoomFactor;
262                     int i = ( int ) Math.floor( valueNew );
263                     colors[row][column] = colorMapO.getColor( i );
264                 }
265             }
266         }
267     } // end mapValuesToColors
268 
269     public void resetRowKeys() {
270         for ( int i = 0; i < m_totalRows; i++ ) {
271             m_rowKeys[i] = i;
272         }
273     }
274 
275     /**
276      * @param row
277      * @param column
278      * @param newColor
279      * @throws ArrayIndexOutOfBoundsException
280      */
281     public void setColor( int row, int column, Color newColor ) throws ArrayIndexOutOfBoundsException {
282 
283         rowRangeCheck( row );
284         colRangeCheck( column );
285 
286         colors[getTrueRowIndex( row )][column] = newColor;
287     }
288 
289     /**
290      * @param colorMap an array of colors which define the midpoints in the color map; this can be one of the constants
291      *        defined in the ColorMap class, like ColorMap.REDGREEN_COLORMAP and ColorMap.BLACKBODY_COLORMAP
292      * @throws IllegalArgumentException if the colorMap array argument contains less than two colors.
293      */
294     public void setColorMap( Color[] colorMap ) throws IllegalArgumentException {
295 
296         if ( colorMap.length < 2 ) {
297             throw new IllegalArgumentException();
298         }
299 
300         this.colorMap = colorMap;
301         mapValuesToColors();
302     }
303 
304     /**
305      * Standardized display range
306      */
307     public void setDisplayRange( double min, double max ) {
308 
309         displayMin = min;
310         displayMax = max;
311 
312         mapValuesToColors();
313     }
314 
315     /**
316      * @param rowKeys
317      */
318     public void setRowKeys( int[] rowKeys ) {
319         m_rowKeys = rowKeys;
320     }
321 
322     /**
323      * Normalizes the elements of a matrix to variance one and mean zero, ignoring missing values todo move this to
324      * matrixstats or something.
325      */
326     public void standardize() {
327 
328         // normalize the data in each row
329         for ( int r = 0; r < m_totalRows; r++ ) {
330             double[] rowValues = getRow( r );
331             DoubleArrayList doubleArrayList = new cern.colt.list.DoubleArrayList( rowValues );
332             DescriptiveWithMissing.standardize( doubleArrayList );
333             rowValues = doubleArrayList.elements();
334             setRow( r, rowValues );
335         }
336 
337         displayMin = -2;
338         displayMax = +2;
339 
340         mapValuesToColors();
341 
342     } // end standardize
343 
344     /**
345      * To be able to sort the rows by an arbitrary key. Creates <code>m_rowKeys</code> array and initializes it in
346      * ascending order from 0 to <code>m_totalRows</code> -1, so that by default it matches the physical order of the
347      * columns: [0,1,2,...,m_totalRows-1]
348      * 
349      * @return int[]
350      */
351     protected int[] createRowKeys() {
352         m_rowKeys = new int[m_totalRows];
353         for ( int i = 0; i < m_totalRows; i++ ) {
354             m_rowKeys[i] = i;
355         }
356         return m_rowKeys;
357     }
358 
359     protected int getTrueRowIndex( int row ) {
360         return m_rowKeys[row];
361     }
362 
363     /**
364      * Changes values in a row, clipping if there are more values than columns.
365      * 
366      * @param row row whose values we want to change
367      * @param values new row values
368      */
369     protected void setRow( int row, double values[] ) {
370 
371         // clip if we have more values than columns
372         int totalValues = Math.min( values.length, m_totalColumns );
373 
374         for ( int column = 0; column < totalValues; column++ ) {
375             maxtrix.set( getTrueRowIndex( row ), column, values[column] );
376 
377         }
378     } // end setRow
379 
380     private void colRangeCheck( int column ) {
381         if ( column >= m_totalColumns )
382             throw new ArrayIndexOutOfBoundsException( "The matrix has only " + m_totalColumns
383                     + " columns, so the maximum possible column index is " + ( m_totalColumns - 1 ) + ". Column index "
384                     + column + " is too high." );
385     }
386 
387     private void init( DoubleMatrix<A, B> matrix ) {
388         if ( matrix == null ) {
389             throw new IllegalArgumentException( "Matrix cannot be null" );
390         }
391         maxtrix = matrix; // by reference, or should we clone?
392         m_totalRows = maxtrix.rows();
393         m_totalColumns = maxtrix.columns();
394         colors = new Color[m_totalRows][m_totalColumns];
395         createRowKeys();
396 
397         displayMin = min = MatrixStats.min( maxtrix );
398         displayMax = max = MatrixStats.max( maxtrix );
399 
400         // map values to colors
401         mapValuesToColors();
402     }
403 
404     private void rowRangeCheck( int row ) {
405         if ( row >= m_totalRows )
406             throw new ArrayIndexOutOfBoundsException( "The matrix has only " + m_totalRows
407                     + " rows, so the maximum possible row index is " + ( m_totalRows - 1 ) + ". Row index " + row
408                     + " is too high." );
409     }
410 
411 } // end class ColorMatrix