001/* 002 * BioJava development code 003 * 004 * This code may be freely disttributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be disttributed 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.Font; 027import java.awt.Graphics2D; 028import java.awt.Rectangle; 029import java.awt.Shape; 030import java.awt.font.FontRenderContext; 031import java.awt.font.GlyphVector; 032import java.awt.geom.AffineTransform; 033import java.awt.geom.Rectangle2D; 034import java.beans.PropertyChangeEvent; 035import java.beans.PropertyChangeListener; 036import java.beans.PropertyChangeSupport; 037import java.util.Comparator; 038import java.util.Iterator; 039import java.util.SortedSet; 040import java.util.TreeSet; 041 042import org.biojava.bio.BioError; 043import org.biojava.bio.BioException; 044import org.biojava.bio.BioRuntimeException; 045import org.biojava.bio.dist.Distribution; 046import org.biojava.bio.seq.io.SymbolTokenization; 047import org.biojava.bio.symbol.FiniteAlphabet; 048import org.biojava.bio.symbol.IllegalSymbolException; 049import org.biojava.bio.symbol.Symbol; 050 051/** 052 * A logo painter that paints in stacked letters. 053 * The total height of the letters is 054 * proportional to the total informaton in the state. The height of each letter 055 * is proportional to its emission probability. The most likely letter is drawn 056 * highest. 057 * 058 * @author Matthew Pocock 059 */ 060public class TextLogoPainter implements LogoPainter { 061 /** 062 * A comparator to set up our letters & information scores nicely. 063 */ 064 private static final Comparator COMP = new ResValComparator(); 065 066 /** 067 * Supports the bean property logoFont. 068 */ 069 private PropertyChangeSupport pcs; 070 071 /** 072 * The property for the logoFont. 073 */ 074 private Font logoFont; 075 076 /** 077 * Retrieve the current font. 078 * 079 * @return the current logo font 080 */ 081 public Font getLogoFont() { 082 return logoFont; 083 } 084 085 /** 086 * Set the current logo font. 087 * 088 * @param logoFont the new Font to render the logo letters in 089 */ 090 public void setLogoFont(Font logoFont) { 091 firePropertyChange("logoFont", this.logoFont, logoFont); 092 this.logoFont = logoFont; 093 } 094 095 public void addPropertyChangeListener(PropertyChangeListener listener) { 096 pcs.addPropertyChangeListener(listener); 097 } 098 099 public void removePropertyChangeListener(PropertyChangeListener listener) { 100 pcs.removePropertyChangeListener(listener); 101 } 102 103 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 104 pcs.addPropertyChangeListener(propertyName, listener); 105 } 106 107 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { 108 pcs.removePropertyChangeListener(propertyName, listener); 109 } 110 111 public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 112 pcs.firePropertyChange(propertyName, oldValue, newValue); 113 } 114 115 public void firePropertyChange(String propertyName, int oldValue, int newValue) { 116 pcs.firePropertyChange(propertyName, oldValue, newValue); 117 } 118 119 public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { 120 pcs.firePropertyChange(propertyName, oldValue, newValue); 121 } 122 123 public void firePropertyChange(PropertyChangeEvent evt) { 124 pcs.firePropertyChange(evt); 125 } 126 127 public boolean hasListeners(String propertyName) { 128 return pcs.hasListeners(propertyName); 129 } 130 131 public void paintLogo(LogoContext ctxt) { 132 Graphics2D g2 = ctxt.getGraphics(); 133 Distribution dist = ctxt.getDistribution(); 134 SymbolStyle style = ctxt.getStyle(); 135 SymbolTokenization toke = null; 136 try { 137 toke = dist.getAlphabet().getTokenization("token"); 138 } catch (BioException ex) { 139 throw new BioRuntimeException(ex); 140 } 141 142 Rectangle bounds = ctxt.getBounds(); 143 double width = bounds.getWidth(); 144 double height = bounds.getHeight(); 145 double base = bounds.getY() + bounds.getHeight(); 146 147 /* This used to have some built-in scaling support, but I've disabled this because 148 * DistributionLogo does scaling too! 149 */ 150 151 // double scale = height * ( 152 // DistributionLogo.totalInformation(dist) / 153 // DistributionLogo.totalBits(dist) 154 // ); 155 156 SortedSet info = new TreeSet(COMP); 157 158 try { 159 for( 160 Iterator i = ((FiniteAlphabet) dist.getAlphabet()).iterator(); 161 i.hasNext(); 162 ) { 163 Symbol s = (Symbol) i.next(); 164 info.add(new ResVal(s, dist.getWeight(s) * height)); 165 } 166 } catch (IllegalSymbolException ire) { 167 throw new BioError("Symbol distsapeared from dist alphabet", ire); 168 } 169 170 FontRenderContext frc = g2.getFontRenderContext(); 171 for(Iterator i = info.iterator(); i.hasNext();) { 172 ResVal rv = (ResVal) i.next(); 173 174 String s = null; 175 try { 176 s = toke.tokenizeSymbol(rv.getToken()); 177 } catch (IllegalSymbolException ex) { 178 throw new BioRuntimeException(ex); 179 } 180 GlyphVector gv = logoFont.createGlyphVector(frc, s); 181 Shape outline = gv.getOutline(); 182 Rectangle2D oBounds = outline.getBounds2D(); 183 184 AffineTransform at = new AffineTransform(); 185 at.setToTranslation(0.0, base-rv.getValue()); 186 at.scale(width / oBounds.getWidth(), rv.getValue() / oBounds.getHeight()); 187 at.translate(-oBounds.getMinX(), -oBounds.getMinY()); 188 outline = at.createTransformedShape(outline); 189 190 try { 191 g2.setPaint(style.fillPaint(rv.getToken())); 192 } catch (IllegalSymbolException ire) { 193 g2.setPaint(Color.black); 194 } 195 g2.fill(outline); 196 197 try { 198 g2.setPaint(style.outlinePaint(rv.getToken())); 199 } catch (IllegalSymbolException ire) { 200 g2.setPaint(Color.gray); 201 } 202 g2.draw(outline); 203 204 base -= rv.getValue(); 205 } 206 } 207 208 public TextLogoPainter() { 209 pcs = new PropertyChangeSupport(this); 210 logoFont = new Font("Tahoma", Font.PLAIN, 12); 211 } 212 213 /** 214 * A symbol/information tuple. 215 */ 216 private static class ResVal { 217 private Symbol symbol; 218 private double value; 219 220 public final Symbol getToken() { 221 return symbol; 222 } 223 224 public final double getValue() { 225 return value; 226 } 227 228 public ResVal(Symbol sym, double val) { 229 symbol = sym; 230 value = val; 231 } 232 } 233 234 /** 235 * The comparator for comparing symbol/information tuples. 236 */ 237 private static class ResValComparator implements Comparator { 238 public final int compare(Object o1, Object o2) { 239 ResVal rv1 = (ResVal) o1; 240 ResVal rv2 = (ResVal) o2; 241 242 double diff = rv1.getValue() - rv2.getValue(); 243 if(diff < 0) return -1; 244 if(diff > 0) return +1; 245 return rv1.getToken().getName().compareTo(rv2.getToken().getName()); 246 } 247 } 248}