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}