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
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
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 }
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;
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
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
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 }
328
329 /***
330 * @return a DenseDoubleMatrix2DNamed object
331 */
332 public AbstractNamedDoubleMatrix getMatrix() {
333 return m_matrix;
334 }
335
336
337
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
376 System.err.println( "Warning: range of values in data is zero." );
377 range = 1.0;
378
379 }
380
381
382 double zoomFactor = ( colorMap.getPaletteSize() - 1 ) / range;
383
384
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
391 m_colors[row][column] = m_missingColor;
392 } else {
393
394
395
396
397
398 if ( value > m_displayMax ) {
399
400 value = m_displayMax;
401 } else if ( value < m_displayMin ) {
402
403 value = m_displayMin;
404 }
405
406
407
408 value -= m_displayMin;
409
410
411 double valueNew = value * zoomFactor;
412 int i = ( int ) Math.round( valueNew );
413 m_colors[row][column] = colorMap.getColor( i );
414 }
415 }
416 }
417 }
418
419 public Object clone() {
420
421 DenseDoubleMatrix2DNamed matrix = new DenseDoubleMatrix2DNamed(
422 m_totalRows, m_totalColumns );
423
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
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
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 }
447
448 }