View Javadoc
1   /*
2    * The baseCode project
3    * 
4    * Copyright (c) 2006 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  import java.awt.Dimension;
23  import java.awt.Font;
24  import java.awt.Graphics;
25  import java.awt.Graphics2D;
26  import java.awt.image.BufferedImage;
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.OutputStream;
30  
31  import javax.imageio.ImageIO;
32  import javax.swing.JPanel;
33  
34  import org.apache.commons.lang3.StringUtils;
35  
36  import ubic.basecode.dataStructure.matrix.DoubleMatrix;
37  import ubic.basecode.graphics.text.Util;
38  
39  /**
40   * A visual component for displaying a color matrix
41   * 
42   * @author Will Braynen
43   * 
44   */
45  public class MatrixDisplay<R, C> extends JPanel {
46  
47      private static final int DEFAULT_SCALE_BAR_WIDTH = 100;
48  
49      private static final long serialVersionUID = -8078532270193813539L;
50  
51      public static <R, C> MatrixDisplay<R, C> newInstance( ColorMatrix<R, C> matrix ) {
52          return new MatrixDisplay<R, C>( matrix );
53      }
54  
55      // data fields
56      ColorMatrix<R, C> colorMatrix; // reference to standardized or unstandardized matrix
57      boolean m_isShowingStandardizedMatrix = false;
58      ColorMatrix<R, C> m_standardizedMatrix;
59  
60      ColorMatrix<R, C> m_unstandardizedMatrix;
61  
62      final int SCALE_BAR_ROOM = 40;
63  
64      protected Dimension m_cellSize = new Dimension( 10, 10 ); // in pixels
65      protected int m_columnLabelHeight; // max
66      protected final int m_defaultResolution = 120;
67      protected int m_fontGutter;
68      protected int m_fontSize = 10;
69      protected boolean m_isShowLabels = false;
70      protected boolean m_isShowScale = false;
71      protected Font m_labelFont = null;
72      protected int m_labelGutter = 5;
73      protected int m_maxColumnLength = 0;
74      protected final int m_maxFontSize = 10;
75      //
76      protected int m_ratioWidth = 0;
77  
78      protected int m_resolution = m_defaultResolution;
79  
80      protected int m_rowLabelWidth; // max
81  
82      protected int m_textSize = 0;
83  
84      public MatrixDisplay( ColorMatrix<R, C> matrix ) {
85          init( matrix );
86      }
87  
88      public MatrixDisplay( DoubleMatrix<R, C> matrix ) {
89          this( new ColorMatrix<R, C>( matrix ) );
90      }
91  
92      public Color getColor( int row, int column ) {
93          return colorMatrix.getColor( row, column );
94      } // end getColor
95  
96      /**
97       * @return the current color map
98       */
99      public Color[] getColorMap() {
100         return colorMatrix.getColorMap();
101     }
102 
103     public ColorMatrix<R, C> getColorMatrix() {
104         return colorMatrix;
105     }
106 
107     public int getColumnCount() {
108         return colorMatrix.getColumnCount();
109     }
110 
111     public Object getColumnName( int column ) {
112         return colorMatrix.getColumnName( column );
113     }
114 
115     public String[] getColumnNames() {
116         return colorMatrix.getColumnNames();
117     }
118 
119     public double getDisplayMax() {
120         return colorMatrix.getDisplayMax();
121     }
122 
123     public double getDisplayMin() {
124         return colorMatrix.getDisplayMin();
125     }
126 
127     public double getDisplayRange() {
128         return colorMatrix.getDisplayMax() - getDisplayMin();
129     }
130 
131     public DoubleMatrix<R, C> getMatrix() {
132         return colorMatrix.getMatrix();
133     }
134 
135     /**
136      * @return the largest value in the matrix
137      */
138     public double getMax() {
139         return colorMatrix.getMax();
140     }
141 
142     /**
143      * @return the m_maxColumnLength
144      */
145     public int getMaxColumnLength() {
146         return m_maxColumnLength;
147     }
148 
149     /**
150      * @return the smallest value in the matrix
151      */
152     public double getMin() {
153         return colorMatrix.getMin();
154     }
155 
156     /**
157      * @return the color used for missing values
158      */
159     public Color getMissingColor() {
160         return colorMatrix.getMissingColor();
161     }
162 
163     public double getRawValue( int row, int column ) {
164         return m_unstandardizedMatrix.getValue( row, column );
165     }
166 
167     public double[] getRow( int row ) {
168         return colorMatrix.getRow( row );
169     }
170 
171     public double[] getRowByName( R rowName ) {
172         return colorMatrix.getRowByName( rowName );
173     }
174 
175     public int getRowCount() {
176         return colorMatrix.getRowCount();
177     }
178 
179     public int getRowHeight() {
180         return m_cellSize.height;
181     }
182 
183     public int getRowIndexByName( R rowName ) {
184         return colorMatrix.getRowIndexByName( rowName );
185     }
186 
187     public Object getRowName( int row ) {
188         return colorMatrix.getRowName( row );
189     }
190 
191     public String[] getRowNames() {
192         return colorMatrix.getRowNames();
193     }
194 
195     public boolean getStandardizedEnabled() {
196 
197         return m_isShowingStandardizedMatrix;
198     }
199 
200     public double getValue( int row, int column ) {
201         return colorMatrix.getValue( row, column );
202     } // end getValue
203 
204     public void init( ColorMatrix<R, C> matrix ) {
205 
206         m_unstandardizedMatrix = colorMatrix = matrix;
207         initSize();
208 
209         // create a standardized copy of the matrix
210         m_standardizedMatrix = matrix.clone();
211         m_standardizedMatrix.standardize();
212 
213     }
214 
215     /**
216      * 
217      */
218     public void resetRowKeys() {
219         colorMatrix.resetRowKeys();
220     }
221 
222     /**
223      * @param outPngFilename String
224      * @param showLabels boolean
225      * @param standardize normalize to deviation 1, mean 0. FIXME this is not used?
226      * @throws IOException
227      */
228     public void saveImage( ColorMatrix<R, C> matrix, String outPngFilename, boolean showLabels, boolean showScalebar,
229             boolean standardize ) throws java.io.IOException {
230 
231         Graphics2D g = null;
232 
233         // Include row and column labels?
234         boolean wereLabelsShown = m_isShowLabels;
235         if ( !wereLabelsShown ) {
236             // Labels aren't visible, so make them visible
237             setLabelsVisible( true );
238             initSize();
239         }
240 
241         // Draw the image to a buffer
242         Dimension d = computeSize( showLabels, showScalebar ); // how big is the image with row and
243         // column labels
244         BufferedImage m_image = new BufferedImage( d.width, d.height, BufferedImage.TYPE_INT_RGB );
245         g = m_image.createGraphics();
246         g.setBackground( Color.white );
247         g.clearRect( 0, 0, d.width, d.height );
248 
249         drawMatrix( matrix, g, showLabels, showScalebar );
250         if ( showLabels ) {
251             drawRowNames( g, showScalebar );
252             drawColumnNames( g, showScalebar );
253         }
254         if ( showScalebar ) {
255             drawScaleBar( g, d, matrix.getDisplayMin(), matrix.getDisplayMax() );
256         }
257 
258         // Write the image to a png file
259         ImageIO.write( m_image, "png", new File( outPngFilename ) );
260 
261         // Restore state: make the image as it was before
262         if ( !wereLabelsShown ) {
263             // Labels weren't visible to begin with, so hide them
264             setLabelsVisible( false );
265             initSize();
266         }
267     } // end saveImage
268 
269     /**
270      * Saves the image to a png file.
271      * 
272      * @param outPngFilename String
273      * @throws IOException
274      */
275     public void saveImage( String outPngFilename ) throws java.io.IOException {
276         saveImage( this.colorMatrix, outPngFilename, m_isShowLabels, m_isShowScale, m_isShowingStandardizedMatrix );
277     }
278 
279     /**
280      * @param outPngFilename
281      * @param showLabels
282      * @throws java.io.IOException
283      */
284     public void saveImage( String outPngFilename, boolean showLabels, boolean showScale ) throws java.io.IOException {
285         saveImage( this.colorMatrix, outPngFilename, showLabels, showScale, m_isShowingStandardizedMatrix );
286     }
287 
288     /**
289      * @param stream
290      * @param showLabels
291      * @param standardize
292      * @throws IOException
293      */
294     public void saveImageToPng( ColorMatrix<R, C> matrix, OutputStream stream, boolean showLabels,
295             boolean showScalebar, boolean standardize ) throws IOException {
296 
297         boolean wasScalebarShown = m_isShowScale;
298         if ( !wasScalebarShown ) {
299             setScaleBarVisible( true );
300             initSize();
301         }
302 
303         // Include row and column labels?
304         boolean wereLabelsShown = m_isShowLabels;
305         if ( !wereLabelsShown ) {
306             // Labels aren't visible, so make them visible
307             setLabelsVisible( true );
308             initSize();
309         }
310 
311         try {
312             writeToPng( matrix, stream, showLabels, showScalebar );
313         } finally {
314             // Restore state: make the image as it was before
315             if ( !wereLabelsShown ) {
316                 // Labels weren't visible to begin with, so hide them
317                 setLabelsVisible( false );
318                 initSize();
319             }
320 
321             if ( !wasScalebarShown ) {
322                 setScaleBarVisible( false );
323                 initSize();
324             }
325         }
326     } // end saveImage
327 
328     public void setCellSize( Dimension d ) {
329 
330         m_cellSize = d;
331         initSize();
332     }
333 
334     /**
335      * @param colorMap an array of colors which define the midpoints in the color map; this can be one of the constants
336      *        defined in the ColorMap class, like ColorMap.REDGREEN_COLORMAP and ColorMap.BLACKBODY_COLORMAP
337      */
338     public void setColorMap( Color[] colorMap ) {
339 
340         m_standardizedMatrix.setColorMap( colorMap );
341         m_unstandardizedMatrix.setColorMap( colorMap );
342     }
343 
344     /**
345      * @param min
346      * @param max
347      */
348     public void setDisplayRange( double min, double max ) {
349         colorMatrix.setDisplayRange( min, max );
350     }
351 
352     /**
353      * If this display component has already been added to the GUI, it will be resized to fit or exclude the row names
354      * 
355      * @param isShowLabels boolean
356      */
357     public void setLabelsVisible( boolean isShowLabels ) {
358         m_isShowLabels = isShowLabels;
359         initSize();
360     }
361 
362     /**
363      * @param matrix the new matrix to use; will resize this display component as necessary
364      */
365     public void setMatrix( ColorMatrix<R, C> matrix ) {
366         colorMatrix = matrix;
367         initSize();
368     }
369 
370     /**
371      * @param columnLength the m_maxColumnLength to set
372      */
373     public void setMaxColumnLength( int columnLength ) {
374         m_maxColumnLength = columnLength;
375     }
376 
377     public void setRowHeight( int height ) {
378 
379         m_cellSize.height = height;
380         initSize();
381     }
382 
383     public void setRowKeys( int[] rowKeys ) {
384         colorMatrix.setRowKeys( rowKeys );
385     }
386 
387     public void setScaleBarVisible( boolean isShowScale ) {
388         m_isShowScale = isShowScale;
389         initSize();
390     }
391 
392     public void setStandardizedEnabled( boolean showStandardizedMatrix ) {
393         m_isShowingStandardizedMatrix = showStandardizedMatrix;
394         if ( showStandardizedMatrix ) {
395             colorMatrix = m_standardizedMatrix;
396         } else {
397             colorMatrix = m_unstandardizedMatrix;
398         }
399     } // end setStandardizedEnabled
400 
401     /**
402      * @param matrix
403      * @param stream
404      * @param showLabels
405      * @param showScalebar
406      */
407     public void writeToPng( ColorMatrix<R, C> matrix, OutputStream stream, boolean showLabels, boolean showScalebar )
408             throws IOException {
409         // Draw the image to a buffer
410         boolean oldLabelSate = this.m_isShowLabels;
411         if ( !oldLabelSate ) {
412             this.setLabelsVisible( true );
413         }
414 
415         Dimension d = computeSize( showLabels, showScalebar );
416         BufferedImage m_image = new BufferedImage( d.width, d.height, BufferedImage.TYPE_INT_RGB );
417         Graphics2D g = m_image.createGraphics();
418         g.setBackground( Color.white );
419         g.clearRect( 0, 0, d.width, d.height );
420         drawMatrix( matrix, g, showLabels, showScalebar );
421         if ( showLabels ) {
422             drawRowNames( g, showScalebar );
423             drawColumnNames( g, showScalebar );
424         }
425         if ( showScalebar ) {
426             drawScaleBar( g, d, matrix.getDisplayMin(), matrix.getDisplayMax() );
427         }
428 
429         // Write the buffered image to the output steam.
430 
431         ImageIO.write( m_image, "png", stream );
432 
433         this.setLabelsVisible( oldLabelSate );
434     }
435 
436     /**
437      * compute the size of the matrix in pixels.
438      * 
439      * @param withLabels
440      * @return
441      */
442     protected Dimension computeSize( boolean showLabels, boolean showScalebar ) {
443 
444         if ( colorMatrix == null ) {
445             return null;
446         }
447 
448         // row label width and height (font-dependent)
449         setFont();
450         m_rowLabelWidth = m_labelGutter
451                 + Util.maxStringPixelWidth( colorMatrix.getRowNames(), this.getFontMetrics( m_labelFont ) );
452         m_rowLabelWidth += m_labelGutter; // this is optional (leaves some space on the right)
453         if ( m_maxColumnLength > 0 ) {
454             String[] cols = colorMatrix.getColumnNames();
455             for ( int i = 0; i < cols.length; i++ ) {
456                 cols[i] = padColumnString( cols[i] );
457 
458             }
459             // fix column height to ~5 pixels per character. This prevents a slightly different column height
460             // for different letters.
461             m_columnLabelHeight = 5 * m_maxColumnLength;
462         } else {
463             m_columnLabelHeight = Util.maxStringPixelWidth( colorMatrix.getColumnNames(),
464                     this.getFontMetrics( m_labelFont ) );
465         }
466 
467         // m_columnLabelHeight += m_labelGutter; // this is optional (leaves some
468         // space on top)
469 
470         // height and width of this display component
471         int height = m_cellSize.height * colorMatrix.getRowCount();
472         int width = m_cellSize.width * colorMatrix.getColumnCount();
473 
474         // adjust for row and column labels
475         if ( showLabels ) {
476             width += m_rowLabelWidth;
477             height += m_columnLabelHeight + m_labelGutter;
478         }
479 
480         if ( showScalebar ) {
481             height += SCALE_BAR_ROOM; /* scale bar height */
482             // if ( width < DEFAULT_SCALE_BAR_WIDTH ) { /* scale bar width */
483             // width = DEFAULT_SCALE_BAR_WIDTH;
484             // }
485         }
486 
487         // set the sizes
488         return new Dimension( width, height );
489 
490     } // end getSize
491 
492     /**
493      * Draws column names vertically (turned 90 degrees counter-clockwise)
494      * 
495      * @param g Graphics
496      */
497     protected void drawColumnNames( Graphics g, boolean leaveRoomForScalebar ) {
498 
499         if ( colorMatrix == null ) return;
500         Color oldColor = g.getColor();
501 
502         int y = m_columnLabelHeight;
503         if ( leaveRoomForScalebar ) {
504             y += SCALE_BAR_ROOM;
505         }
506 
507         g.setColor( Color.white );
508         g.fillRect( 0, 0, this.getWidth(), y );
509 
510         g.setColor( Color.black );
511         g.setFont( m_labelFont );
512 
513         int columnCount = colorMatrix.getColumnCount();
514         for ( int j = 0; j < columnCount; j++ ) {
515             // compute the coordinates
516             int x = m_cellSize.width + j * m_cellSize.width - m_fontGutter;
517 
518             Object columnName = colorMatrix.getColumnName( j );
519             if ( null == columnName ) {
520                 columnName = "Undefined";
521             }
522 
523             // fix the name length as 20 characters
524             // add ellipses (...) if > 20
525             // spacepad to 20 if < 20
526             String columnNameString = columnName.toString();
527             if ( m_maxColumnLength > 0 ) {
528                 columnNameString = padColumnString( columnNameString );
529             }
530 
531             // print the text vertically
532             Util.drawVerticalString( g, columnNameString, m_labelFont, x, y );
533 
534         }
535         g.setColor( oldColor );
536     } // end drawColumnNames
537 
538     /**
539      * Gets called from #paintComponent and #saveImage. Does not draw the labels.
540      * 
541      * @param g Graphics
542      * @param leaveRoomForLabels boolean
543      */
544     protected void drawMatrix( ColorMatrix<R, C> matrix, Graphics g, boolean leaveRoomForLabels,
545             boolean leaveRoomForScalebar ) {
546 
547         // fill in background with white.
548         Color oldColor = g.getColor();
549         g.setColor( Color.white );
550         g.fillRect( 0, 0, this.getWidth(), this.getHeight() );
551 
552         int rowCount = matrix.getRowCount();
553         int columnCount = matrix.getColumnCount();
554 
555         // loop through the matrix, one row at a time
556         for ( int i = 0; i < rowCount; i++ ) {
557             int y = i * m_cellSize.height;
558             if ( leaveRoomForLabels ) {
559                 y += m_columnLabelHeight + m_labelGutter;
560             }
561             if ( leaveRoomForScalebar ) {
562                 y += SCALE_BAR_ROOM;
563             }
564 
565             // draw an entire row, one cell at a time
566             for ( int j = 0; j < columnCount; j++ ) {
567                 int x = j * m_cellSize.width;
568                 int width = ( j + 1 ) * m_cellSize.width - x;
569 
570                 Color color = matrix.getColor( i, j );
571                 g.setColor( color );
572                 g.fillRect( x, y, width, m_cellSize.height );
573             }
574 
575         }
576         g.setColor( oldColor );
577     }
578 
579     /**
580      * Draws row names (horizontally)
581      * 
582      * @param g Graphics
583      * @param showScalebar
584      */
585     protected void drawRowNames( Graphics g, boolean showScalebar ) {
586 
587         if ( colorMatrix == null ) return;
588 
589         Color oldColor = g.getColor();
590 
591         g.setColor( Color.white );
592         g.fillRect( colorMatrix.getColumnCount() * m_cellSize.width, 0,
593                 colorMatrix.getColumnCount() * m_cellSize.width, this.getHeight() );
594 
595         int rowCount = colorMatrix.getRowCount();
596         int xLabelStartPosition = colorMatrix.getColumnCount() * m_cellSize.width + m_labelGutter;
597         g.setColor( Color.black );
598         g.setFont( m_labelFont );
599 
600         for ( int i = 0; i < rowCount; i++ ) {
601             int y = i * m_cellSize.height + m_columnLabelHeight + m_labelGutter;
602             int yLabelPosition = y + m_cellSize.height - m_fontGutter;
603             if ( showScalebar ) {
604                 yLabelPosition += SCALE_BAR_ROOM;
605             }
606 
607             Object rowName = colorMatrix.getRowName( i );
608             if ( null == rowName ) {
609                 rowName = "Undefined";
610             }
611 
612             g.drawString( rowName.toString(), xLabelStartPosition, yLabelPosition );
613 
614         } // end drawing row names
615         g.setColor( oldColor );
616     } // end rawRowName
617 
618     /**
619      * @param g
620      * @param d
621      */
622     protected void drawScaleBar( Graphics g, Dimension d, double displayMin, double displayMax ) {
623         /*
624          * FIXME this is all a bit of a hack
625          */
626         g.setColor( Color.white );
627         int upperLeftScalebarGutter = 10;
628         int scaleBarHeight = 10; // these and text height have to total < SCALE_BAR_ROOM
629         int desiredScaleBarLength = ( int ) Math.min( DEFAULT_SCALE_BAR_WIDTH, d.getWidth() );
630 
631         if ( desiredScaleBarLength < 10 ) {
632             return;
633         }
634 
635         g.drawRect( upperLeftScalebarGutter, upperLeftScalebarGutter, desiredScaleBarLength, upperLeftScalebarGutter );
636         JGradientLabell#JGradientLabel">JGradientLabel scalebar = new JGradientLabel( new ColorMap( this.getColorMap() ).getPalette() );
637         scalebar.setBackground( Color.white );
638         scalebar.setSize( new Dimension( desiredScaleBarLength, scaleBarHeight ) );
639         int actualWidth = scalebar.drawAtLocation( g, upperLeftScalebarGutter, upperLeftScalebarGutter );
640         g.setColor( Color.black );
641         g.drawString( String.format( "%.2g", displayMin ), 0, upperLeftScalebarGutter + scaleBarHeight + m_fontGutter
642                 + g.getFontMetrics().getHeight() );
643         g.drawString( String.format( "%.2g", displayMax ), actualWidth, upperLeftScalebarGutter + scaleBarHeight
644                 + m_fontGutter + g.getFontMetrics().getHeight() );
645         g.drawRect( upperLeftScalebarGutter, upperLeftScalebarGutter, actualWidth, scaleBarHeight );
646     }
647 
648     /**
649      * Sets the display size
650      */
651     protected void initSize() {
652 
653         Dimension d = getSize();
654         setMinimumSize( d );
655         setPreferredSize( d );
656         setSize( d );
657         this.revalidate();
658     }
659 
660     /*
661      * (non-Javadoc)
662      * 
663      * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
664      */
665     @Override
666     protected void paintComponent( Graphics g ) {
667 
668         super.paintComponent( g );
669         drawMatrix( colorMatrix, g, m_isShowLabels, m_isShowScale );
670 
671         if ( m_isShowLabels ) {
672             drawRowNames( g, m_isShowScale );
673             drawColumnNames( g, m_isShowScale );
674         }
675         if ( m_isShowScale ) {
676             drawScaleBar( g, this.getSize(), colorMatrix.getDisplayMin(), colorMatrix.getDisplayMax() );
677         }
678     } // end paintComponent
679 
680     /**
681      * @return the height of the font
682      */
683     private int getFontSize() {
684         return Math.max( m_cellSize.height, 5 );
685     }
686 
687     /**
688      * Pads a string to the maxColumnLength. If it is over the maxColumnLength, it abbreviates it to the maxColumnLength
689      * 
690      * @param str
691      * @return
692      */
693     private String padColumnString( String str ) {
694         String paddedstr = StringUtils.abbreviate( str, m_maxColumnLength );
695         paddedstr = StringUtils.rightPad( str, m_maxColumnLength, " " );
696         return paddedstr;
697     }
698 
699     /**
700      * Sets the font used for drawing text
701      */
702     private void setFont() {
703         int fontSize = Math.min( getFontSize(),
704                 ( int ) ( ( double ) m_maxFontSize / ( double ) m_defaultResolution * m_resolution ) );
705         if ( fontSize != m_fontSize || m_labelFont == null ) {
706             m_fontSize = fontSize;
707             m_labelFont = new Font( "Ariel", Font.PLAIN, m_fontSize );
708             m_fontGutter = ( int ) ( m_cellSize.height * .22 );
709         }
710     }
711 
712 } // end class MatrixDisplay
713