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 */
021package org.biojava.bio.gui.sequence;
022
023import java.awt.Color;
024import java.awt.Graphics2D;
025import java.awt.event.MouseEvent;
026import java.awt.geom.Line2D;
027import java.awt.geom.Rectangle2D;
028import java.util.List;
029
030import org.biojava.bio.BioError;
031import org.biojava.bio.program.abi.ABITrace;
032import org.biojava.bio.seq.DNATools;
033import org.biojava.bio.symbol.IllegalSymbolException;
034import org.biojava.utils.AbstractChangeable;
035import org.biojava.utils.ChangeEvent;
036import org.biojava.utils.ChangeSupport;
037import org.biojava.utils.ChangeType;
038import org.biojava.utils.ChangeVetoException;
039
040/**
041 * Renders an ABI trace file as a chromatogram graph.
042 *
043 * @author Matthew Pocock
044 * @author Mark Schreiber
045 */
046public class AbiTraceRenderer
047extends AbstractChangeable
048implements SequenceRenderer {
049  public static final ChangeType TRACE = new ChangeType(
050    "The trace has changed",
051    AbiTraceRenderer.class,
052    "TRACE",
053    SequenceRenderContext.LAYOUT
054  );
055
056  public static final ChangeType DEPTH = new ChangeType(
057    "The trace render depth has changed",
058    AbiTraceRenderer.class,
059    "DEPTH",
060    SequenceRenderContext.LAYOUT
061  );
062
063  private ABITrace trace;
064  private double depth;
065
066  public AbiTraceRenderer() {
067  }
068
069  public void paint(Graphics2D g, SequenceRenderContext ctxt) {
070    if(ctxt.getDirection() == SequenceRenderContext.VERTICAL || trace == null) {
071      return;
072    }
073
074    try {
075      Rectangle2D clip = g.getClip().getBounds2D();
076
077      int min = Math.max(ctxt.getRange().getMin(), ctxt.graphicsToSequence(clip.getMinX()));
078      int max = Math.min(ctxt.getRange().getMax(), ctxt.graphicsToSequence(clip.getMaxX()));;
079      int[] baseCalls = trace.getBasecalls();
080      int[] traceA = trace.getTrace(DNATools.a());
081      int[] traceG = trace.getTrace(DNATools.g());
082      int[] traceC = trace.getTrace(DNATools.c());
083      int[] traceT = trace.getTrace(DNATools.t());
084
085      g.setColor(Color.green);
086      renderTrace(baseCalls, traceA, g, ctxt, min, max);
087      g.setColor(Color.black);
088      renderTrace(baseCalls, traceG, g, ctxt, min, max);
089      g.setColor(Color.blue);
090      renderTrace(baseCalls, traceC, g, ctxt, min, max);
091      g.setColor(Color.red);
092      renderTrace(baseCalls, traceT, g, ctxt, min, max);
093    } catch (IllegalSymbolException ise) {
094      throw new BioError("Can't process trace file", ise);
095    }
096  }
097
098  private void renderTrace(int[] baseCalls, int[] trace, Graphics2D g, SequenceRenderContext ctxt, int min, int max) {
099    double depthScale = depth / 2000.0; // assume X gredations
100    Line2D line = new Line2D.Float();
101    for(int pos = min; pos <= max; pos++) {
102      int minT;
103      int maxT;
104
105      if(pos == 1) {
106        minT = 0;
107      } else {
108        minT = (baseCalls[pos - 2] + baseCalls[pos - 1]) / 2;
109      }
110
111      if(pos == baseCalls.length) {
112        maxT = trace.length - 1;
113      } else {
114        maxT = (baseCalls[pos - 1] + baseCalls[pos - 0]) / 2;
115      }
116
117      double scale = ctxt.getScale() / (double) (maxT - minT);
118
119      double stg = ctxt.sequenceToGraphics(pos);
120      for(int i = minT; i < maxT; i++) {
121        int j = i - minT;
122        line.setLine(
123          stg + scale * (0.5 + j),
124          depth - trace[i] * depthScale,
125          stg + scale * (0.5 + j + 1),
126          depth - trace[i + 1] * depthScale
127        );
128
129        g.draw(line);
130      }
131    }
132  }
133
134  public void setTrace(ABITrace trace)
135  throws ChangeVetoException {
136    ChangeSupport cs = getChangeSupport(TRACE);
137    synchronized(cs) {
138      ChangeEvent ce = new ChangeEvent(this, TRACE, trace, this.trace);
139      cs.firePreChangeEvent(ce);
140      this.trace = trace;
141      cs.firePostChangeEvent(ce);
142    }
143  }
144
145  public ABITrace getTrace() {
146    return trace;
147  }
148
149  public void setDepth(double depth)
150  throws ChangeVetoException {
151    if(depth < 0.0) {
152      throw new ChangeVetoException("Can't set depth to a negative number: " + depth);
153    }
154
155    ChangeSupport cs = getChangeSupport(DEPTH);
156    synchronized(cs) {
157      ChangeEvent ce = new ChangeEvent(this, DEPTH, new Double(depth), new Double(this.depth));
158      cs.firePreChangeEvent(ce);
159      this.depth = depth;
160      cs.firePostChangeEvent(ce);
161    }
162  }
163
164  public double getDepth(SequenceRenderContext src) {
165    if(src.getDirection() == SequenceRenderContext.HORIZONTAL && trace != null) {
166      return depth;
167    } else {
168      return 0.0;
169    }
170  }
171
172  public double getMinimumLeader(SequenceRenderContext src) {
173    return 0.0;
174  }
175
176  public double getMinimumTrailer(SequenceRenderContext src) {
177    return 0.0;
178  }
179
180  public SequenceViewerEvent processMouseEvent(
181    SequenceRenderContext src,
182    MouseEvent me,
183    List path
184  ) {
185    // don't do anything
186    return null;
187  }
188}
189