001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022 023package org.biojava.bio.gui; 024 025import java.awt.Color; 026import java.awt.Dimension; 027import java.awt.Graphics; 028import java.awt.Graphics2D; 029import java.awt.Rectangle; 030import java.awt.RenderingHints; 031import java.beans.PropertyChangeEvent; 032import java.beans.PropertyChangeListener; 033import java.util.Iterator; 034 035import javax.swing.JComponent; 036 037import org.biojava.bio.BioError; 038import org.biojava.bio.dist.Distribution; 039import org.biojava.bio.symbol.FiniteAlphabet; 040import org.biojava.bio.symbol.IllegalAlphabetException; 041import org.biojava.bio.symbol.IllegalSymbolException; 042import org.biojava.bio.symbol.Symbol; 043 044/** 045 * The GUI component for rendering a DistributionLogo. By default, this uses the text 046 * logo style - with letters stacked on top of one another, scaled by the total 047 * information in the dist, and uses a PlainStyle colorer that outlines in 048 * black, and fills in grey. 049 * 050 * @author Matthew Pocock 051 */ 052public class DistributionLogo extends JComponent { 053 /** 054 * The default logo painter. 055 */ 056 private static final LogoPainter DEFAULT_LOGO_PAINTER = new TextLogoPainter(); 057 private static final BlockPainter DEFAULT_BLOCK_PAINTER = new PlainBlock(); 058 059 /** 060 * A usefull constant to keep arround. 061 */ 062 private static double bits = Math.log(2.0); 063 064 /** 065 * The dist to render. 066 */ 067 private Distribution dist; 068 069 /** 070 * The logoPainter property. 071 */ 072 private LogoPainter logoPainter = DEFAULT_LOGO_PAINTER; 073 074 /** 075 * The style property. 076 */ 077 private SymbolStyle style = new PlainStyle(Color.black, Color.gray); 078 079 /** 080 * The information/plain property 081 */ 082 boolean scaleByInformation = true; 083 084 private BlockPainter blockPainter = DEFAULT_BLOCK_PAINTER; 085 private RenderingHints renderingHints = null; 086 087 /** 088 * Retrieve the currently rendered dist. 089 * 090 * @return a Distribution 091 */ 092 public Distribution getDistribution() { 093 return dist; 094 } 095 096 /** 097 * <p> 098 * Set the dist to render. 099 * </p> 100 * 101 * <p> 102 * The dist must be over a FiniteAlphabet so that we can draw the numbers 103 * for each Symbol. 104 * </p> 105 * 106 * @param dist the new Distribution to render 107 */ 108 public void setDistribution(Distribution dist) 109 throws IllegalAlphabetException { 110 firePropertyChange("dist", this.dist, dist); 111 this.dist = dist; 112 } 113 114 /** 115 * Retrieve the current logo painter. 116 * 117 * @return the LogoPainter used to render the dist 118 */ 119 public LogoPainter getLogoPainter() { 120 return logoPainter; 121 } 122 123 /** 124 * <p> 125 * Set the logo painter. 126 * </p> 127 * 128 * <p> 129 * This will alter the way that the dist is rendered to screen. 130 * </p> 131 * 132 * @param logoPainter the new logoPainter 133 */ 134 public void setLogoPainter(LogoPainter logoPainter) { 135 firePropertyChange("logoPainter", this.logoPainter, logoPainter); 136 this.logoPainter = logoPainter; 137 } 138 139 /** 140 * Retrieve the current style. 141 * 142 * @return the current SymbolStyle 143 */ 144 public SymbolStyle getStyle() { 145 return style; 146 } 147 148 /** 149 * <p> 150 * Set the symbol style. 151 * </p> 152 * 153 * <p> 154 * This will change the outline and fill paints for the logos 155 * </p> 156 * 157 * @param style the new SymbolStyle to use 158 */ 159 public void setStyle(SymbolStyle style) { 160 firePropertyChange("style", this.style, style); 161 this.style = style; 162 } 163 164 public boolean isScaleByInformation() { 165 return scaleByInformation; 166 } 167 168 public void setScaleByInformation(boolean scale) { 169 this.scaleByInformation = scale; 170 } 171 172 public BlockPainter getBlockPainter() { 173 return blockPainter; 174 } 175 176 public void setBlockPainter(BlockPainter blockPainter) { 177 this.blockPainter = blockPainter; 178 } 179 180 /** 181 * Create a new DistributionLogo object. It will set up all the properties except the 182 * dist to render. 183 */ 184 public DistributionLogo() { 185 this.addPropertyChangeListener(new PropertyChangeListener() { 186 public void propertyChange(PropertyChangeEvent pce) { 187 String name = pce.getPropertyName(); 188 if(name.equals("dist") || 189 name.equals("logoPainter") || 190 name.equals("style") ) 191 { 192 repaint(); 193 } 194 } 195 }); 196 197 Dimension d = new Dimension(20, 20); 198 setMinimumSize(d); 199 setPreferredSize(d); 200 } 201 202 /** 203 * Calculate the information content of a symbol in bits. 204 * 205 * @param s the symbol to calculate for 206 * @param dist the <code>Distribution</code> that the symbol comes from and 207 * in whose context the information content will be calculated. 208 * @throws IllegalSymbolException if r is not within the dist. 209 */ 210 public static double entropy(Distribution dist, Symbol s) throws IllegalSymbolException { 211 double p = dist.getWeight(s); 212 if (p == 0.0) { 213 return 0; 214 } 215 double lp = Math.log(p); 216 217 return -p * lp / bits; 218 } 219 220 /** 221 * Retrieve the maximal number of bits possible for this type of dist. 222 * 223 * @return maximum bits as a double 224 */ 225 public static double totalBits(Distribution dist) { 226 return Math.log(((FiniteAlphabet) dist.getAlphabet()).size()) / bits; 227 } 228 229 /** 230 * <p> 231 * Calculates the total information of the dist in bits. 232 * </p> 233 * 234 * <p> 235 * This calculates <code>totalBits - sum_r(entropy(r))</code> 236 * </p> 237 * 238 * @return the total information in the dist 239 */ 240 public static double totalInformation(Distribution dist) { 241 double inf = totalBits(dist); 242 243 for( 244 Iterator i = ((FiniteAlphabet) dist.getAlphabet()).iterator(); 245 i.hasNext(); 246 ) { 247 Symbol s = (Symbol) i.next(); 248 try { 249 inf -= entropy(dist, s); 250 } catch (IllegalSymbolException ire) { 251 throw new BioError( 252 "Symbol evaporated while calculating information", ire); 253 } 254 } 255 256 return inf; 257 } 258 259 /** 260 * Transforms the graphics context so that it is in bits space, 261 * and then requests the logo painter to fill the area. 262 */ 263 public void paintComponent(Graphics g) { 264 final Graphics2D g2 = (Graphics2D) g; 265 266 if(renderingHints != null){ 267 g2.setRenderingHints(renderingHints); 268 } 269 270 Rectangle clip = g2.getClipBounds(); 271 if(isOpaque()) { 272 g2.clearRect(clip.x, clip.y, clip.width, clip.height); 273 } 274 if(getDistribution() == null) { 275 return; 276 } 277 278 final Rectangle bounds = getBounds(); 279 if(isScaleByInformation()) { 280 int height = bounds.height; 281 double scale = height * (totalInformation(getDistribution()) / totalBits(getDistribution())); 282 bounds.height = (int) scale; 283 bounds.y += (int) (height - scale); 284 } 285 286 LogoContext ctxt = new LogoContext() { 287 public Graphics2D getGraphics() { 288 return g2; 289 } 290 public Distribution getDistribution() { 291 return DistributionLogo.this.getDistribution(); 292 } 293 public Rectangle getBounds() { 294 return bounds; 295 } 296 public SymbolStyle getStyle() { 297 return DistributionLogo.this.getStyle(); 298 } 299 public BlockPainter getBlockPainter() { 300 return DistributionLogo.this.getBlockPainter(); 301 } 302 }; 303 getLogoPainter().paintLogo(ctxt); 304 } 305 306 public RenderingHints getRenderingHints() { 307 return renderingHints; 308 } 309 310 public void setRenderingHints(RenderingHints renderingHints) { 311 this.renderingHints = renderingHints; 312 } 313}