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
022package org.biojava.bio.gui.sequence;
023
024import java.awt.Graphics2D;
025import java.awt.Paint;
026import java.awt.event.MouseEvent;
027import java.util.Arrays;
028import java.util.Iterator;
029
030import org.biojava.bio.Annotation;
031import org.biojava.bio.BioError;
032import org.biojava.bio.BioException;
033import org.biojava.bio.seq.ComponentFeature;
034import org.biojava.bio.seq.DNATools;
035import org.biojava.bio.seq.Feature;
036import org.biojava.bio.seq.FeatureFilter;
037import org.biojava.bio.seq.FeatureHolder;
038import org.biojava.bio.seq.Sequence;
039import org.biojava.bio.seq.SimpleAssembly;
040import org.biojava.bio.seq.StrandedFeature;
041import org.biojava.bio.symbol.Location;
042import org.biojava.bio.symbol.RangeLocation;
043import org.biojava.utils.AbstractChangeable;
044import org.biojava.utils.ChangeVetoException;
045
046/**
047 * A feature renderer that computes the data necessary to render
048 * multi-exon transcripts without CDS data.
049 * <p>
050 * The actual drawing is done by a child renderer.  In this case,
051 * SixFrameRenderer is used, which can use data from this renderer
052 * to display transcripts in the correct translation frames.
053 *
054 * @author David Huen
055 */
056
057public class SixFrameZiggyRenderer
058  extends AbstractChangeable
059  implements FeatureRenderer, java.io.Serializable {
060  private SixFrameRenderer pane;
061  public SixFrameZiggyRenderer(SixFrameRenderer pane) {
062    this.pane = pane;
063  }
064
065  public void setFill(Paint p)
066    throws ChangeVetoException {
067    pane.setFill(p);
068  }
069
070  public Paint getFill() {
071    return pane.getFill();
072  }
073
074  public void setOutline(Paint p)
075    throws ChangeVetoException {
076    pane.setOutline(p);
077  }
078
079  public Paint getOutline() {
080    return pane.getOutline();
081  }
082
083  public void setBlockDepth(double depth)
084    throws ChangeVetoException {
085    pane.setBlockWidth(depth);
086  }
087
088  public double getBlockDepth() {
089    return pane.getBlockWidth();
090  }
091
092  public double getDepth(SequenceRenderContext src) {
093    return pane.getDepth(src);
094  }
095
096  private boolean isStop(
097                    Sequence seq,
098                    int base,
099                    StrandedFeature.Strand strand) {
100    // tests whether there is a stop at given location.
101    // the triplet is either base, +1, +2 or -1, -2
102    // depending on the strand searched
103    if (strand == StrandedFeature.POSITIVE) {
104      // search top strand
105      // first base must be t
106      if (seq.symbolAt(base) != DNATools.t()) return false;
107
108      // second base cannot be c or t
109      if (seq.symbolAt(base+1) == DNATools.c()) return false;
110      if (seq.symbolAt(base+1) == DNATools.t()) return false;
111
112      // if second base is g, the third must be a
113      if (seq.symbolAt(base+1) == DNATools.g()) {
114        if (seq.symbolAt(base+2) != DNATools.a()) return false;
115      }
116      else {
117        // second base is a: third must be a or g.
118        if (seq.symbolAt(base+2) == DNATools.c()) return false;
119        if (seq.symbolAt(base+2) == DNATools.t()) return false;
120      }
121
122      // oh well, must be a stop, innit?
123      return true;
124
125    } else {
126      // search bottom strand
127      // first base must be t
128      if (seq.symbolAt(base) != DNATools.a()) return false;
129
130      // second base cannot be c or t on reverse strand
131      if (seq.symbolAt(base-1) == DNATools.a()) return false;
132      if (seq.symbolAt(base-1) == DNATools.g()) return false;
133
134      // if second base is g, the third must be a
135      if (seq.symbolAt(base-1) == DNATools.c()) {
136        if (seq.symbolAt(base-2) != DNATools.t()) return false;
137      }
138      else {
139        // second base is a: third must be a or g.
140        if (seq.symbolAt(base-2) == DNATools.a()) return false;
141        if (seq.symbolAt(base-2) == DNATools.g()) return false;
142      }
143
144      // ach! a stop!
145      return true;
146    }
147  }
148
149  private int findORF(
150                Sequence seq,
151                StrandedFeature.Strand strand) {
152    // finds in a SymbolList the specified phase with
153    // longest ORF and returns the phase.
154    int[] lastStop = {0, 0, 0};
155//    int[] longestORF = {0, 0, 0};
156    int bestPhase = 0;
157    int highestORFSize = 0;
158
159    // scan thru' the sequence looking for stops
160    int length = seq.length();
161    if (length < 4) return 0;
162
163    // set limits of search
164    int startSearch, endSearch;
165    if (strand == StrandedFeature.POSITIVE) {
166      startSearch = 1;
167      endSearch = length - 2;
168    }
169    else {
170      startSearch = 3;
171      endSearch = length;
172    }
173
174    for (int i=startSearch; i <= endSearch; i++) {
175      if (isStop(seq, i, strand)) {
176        // stop found
177        int phase = i%3;
178        int currORFSize = i - lastStop[phase];
179
180        // is this a candidate for best phase?
181        if (currORFSize > highestORFSize) {
182          bestPhase = phase;
183          highestORFSize= currORFSize;
184//          longestORF[phase] = currORFSize;
185        }
186        lastStop[phase] = i;
187//        System.out.println("findORF i phase, largest: " + i + " "
188//           + phase + " " + currORFSize);
189      }
190    }
191
192    // there is always the possibility that there are a few stops
193    // near the beginning then no more.
194    // The best phase will then be misdetected.
195    // Assume closure at end of frame.
196    for (int i=0; i < 3; i++) {
197      int currORFSize = endSearch - lastStop[i];
198      if (currORFSize > highestORFSize) {
199        bestPhase = i;
200        highestORFSize= currORFSize;
201//        longestORF[phase] = currORFSize;
202      }
203    }
204
205    return bestPhase;
206  }
207
208  private Sequence assembleFusedSequence(Feature [] block, Sequence seq) {
209    // assembles a fused sequence from component features
210    // only assembles in the forward direction but will
211    // sort exons as necessary.
212
213    SimpleAssembly sa = new SimpleAssembly("temp", "temp");
214    ComponentFeature.Template cft = new ComponentFeature.Template();
215    cft.annotation = Annotation.EMPTY_ANNOTATION;
216    cft.strand = StrandedFeature.POSITIVE;
217    cft.componentSequence = seq;
218
219    int last = 0;
220    for (int j= 0; j < block.length; j++) {
221      // fuse all "exons" irrespective of orientation.
222      Feature thisExon = block[j];
223
224      cft.componentLocation = thisExon.getLocation();
225      int length = cft.componentLocation.getMax() -
226                     cft.componentLocation.getMin() + 1;
227      cft.location = new RangeLocation(last+1, last+length);
228      last += length;
229//      System.out.println("assemble: " + cft.componentLocation.getMin() + " " + cft.componentLocation.getMax());
230
231      try {
232        sa.createFeature(cft);
233      }
234      catch (BioException be) {
235        throw new BioError(
236          "Couldn't merge exons.", be
237          );
238      }
239      catch (ChangeVetoException cve) {
240        throw new BioError(
241
242          "Couldn't merge exons.",cve
243          );
244      }
245    }
246    return sa;
247  }
248
249  public void renderFeature(
250                Graphics2D g,
251                Feature f,
252                SequenceRenderContext context) {
253//    System.out.println("SixFrameZiggyRenderer called");
254
255    if (!(f instanceof StrandedFeature)) return;
256    // create a fused version of the transcript
257    // this solution is ugly as hell, a botched abortion of a fix
258    //
259    // the algorithm is hideously simple.  Irrespective of the
260    // strandedness of the transcript, a fused sequence will be
261    // generated in the forward direction.
262    //
263    // this "transcript" will then be searched for the longest
264    // ORF in the correct strand and the phase of the largest ORF
265    // returned.  It really doesn't matter whether the min sequence
266    // end is the 5' or 3' of the transcript as phase is consistent
267    // thru' an ORF.
268    //
269    // By just passing the best phase over to SixFrameRenderer, the
270    // the phase of successive exons can be computed from just the
271    // previous exon phase and the preceding intron size.
272
273    //filter for only the exons
274    FeatureFilter filt = new FeatureFilter.ByType("exon");
275    FeatureHolder exons = f.filter(filt, false);
276
277    // sort the returned exons in ascending order
278    // disappointment...
279    int featureCount = exons.countFeatures();
280    Feature[] orderedExons = new Feature[featureCount];
281    int i=0;
282    for (Iterator fi=exons.features(); fi.hasNext();) {
283      orderedExons[i++] = (Feature) fi.next();
284    }
285    Arrays.sort(orderedExons, new Feature.ByLocationComparator());
286
287    Sequence fused = assembleFusedSequence(orderedExons, f.getSequence());
288
289    StrandedFeature.Strand strand = ((StrandedFeature) f).getStrand();
290
291    // findORF will find the best phase within the "ORF" but that
292    //  needs to be corrected for the phase in which the ORF is
293    // embedded into the sequence
294    int phase = findORF(fused, strand);
295
296//    System.out.println("fused length, phase, strand: " + fused.length() + " "
297//                        + phase + " " + strand);
298//    System.out.println("sequence is :- " + fused.seqString());
299
300    // Iterate over exon child features: these are already ordered.
301    Location loc = null;
302    for (i = 0; i < orderedExons.length; i++) {
303      loc = ((Feature) orderedExons[i]).getLocation();
304      if (i == 0) {
305        // first exon
306        pane.startZiggy(strand, (2 + loc.getMin() + phase)%3);
307        pane.renderLocation(g, context, loc);
308//        System.out.println("block value is " + loc);
309      }
310      else {
311        pane.renderLocation(g, context, loc);
312//        System.out.println("block value is " + loc);
313      }
314    }
315
316  }
317
318  public FeatureHolder processMouseEvent(
319    FeatureHolder hits,
320    SequenceRenderContext src,
321    MouseEvent me
322  ) {
323    return hits;
324  }
325}