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}