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.Color;
025import java.awt.Graphics2D;
026import java.awt.Paint;
027import java.awt.event.MouseEvent;
028import java.awt.geom.Line2D;
029import java.awt.geom.Point2D;
030import java.awt.geom.Rectangle2D;
031import java.util.List;
032import java.util.Vector;
033
034import org.biojava.bio.seq.StrandedFeature;
035import org.biojava.bio.symbol.Location;
036import org.biojava.bio.symbol.RangeLocation;
037import org.biojava.utils.AbstractChangeable;
038import org.biojava.utils.ChangeEvent;
039import org.biojava.utils.ChangeSupport;
040import org.biojava.utils.ChangeVetoException;
041 
042/**
043 * Class that handles drawing in six frames for other
044 * classes.
045 *
046 * Suitable for use with CDS features particularly.
047 * <p>
048 * Partly based on Matthew Pocock's ZiggyFeatureRenderer.
049 *
050 * @author David Huen
051 */
052
053public class SixFrameRenderer 
054    extends AbstractChangeable 
055    implements SequenceRenderer {
056  // this really ought to derive from a different class since
057  // it doesn't implement a functional paint method.
058  private double bandWidth = 25.0;
059  private double blockWidth= 20.0;
060  private Paint outline = Color.blue;
061  private Paint fill = Color.red;
062  private Paint lines = Color.black;
063
064  // the following ought to be exported elsewhere later
065  // this is not threadsafe!
066  StrandedFeature.Strand strand;
067  boolean  zigPrecedesBlock, phaseKnown;
068  Point2D.Double zigOriginP;
069  
070  int zigOriginS;
071  int moduloFrame;
072//  double midpointOffset;
073  double offsetPrevBlock;
074  public SixFrameRenderer() {
075  }
076
077  public double getDepth(SequenceRenderContext src) {
078    return 6.0 * bandWidth + 1;
079  }
080
081  public double getMinimumLeader(SequenceRenderContext src) {
082    return 0.0;
083  }
084 
085  public double getMinimumTrailer(SequenceRenderContext src) {
086    return 0.0;
087  }
088
089  private double frameOffset(
090                   int moduloFrame, 
091                   StrandedFeature.Strand strand) {
092    // computes the offset for a given frame
093    if (strand == StrandedFeature.NEGATIVE) {
094      return bandWidth * (moduloFrame + 3);
095    }
096    else {
097      // default is positive strand too
098      return bandWidth * moduloFrame;
099    }
100  }
101
102  public void setFill(Paint p)
103  throws ChangeVetoException {
104    if(hasListeners()) {
105      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
106      synchronized(cs) {
107        ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT);
108        cs.firePreChangeEvent(ce);
109        this.fill = p;
110        cs.firePostChangeEvent(ce);
111      }
112    } else {
113      this.fill = p;
114    }
115  }
116 
117  public Paint getFill() {
118    return fill;
119  }
120 
121  public void setOutline(Paint p)
122  throws ChangeVetoException {
123    if(hasListeners()) {
124      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
125      synchronized(cs) {
126        ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT);
127        cs.firePreChangeEvent(ce);
128        this.outline = p;
129        cs.firePostChangeEvent(ce);
130      }
131    } else {
132      this.outline = p;
133    }
134  }
135 
136  public Paint getOutline() {
137    return outline;
138  }
139 
140  public void setBlockWidth(double width)
141  throws ChangeVetoException {
142    if(hasListeners()) {
143      ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
144      synchronized(cs) {
145        ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT);
146        cs.firePreChangeEvent(ce);
147        this.blockWidth = width;
148        cs.firePostChangeEvent(ce);
149      }
150    } else {
151      this.blockWidth = width;
152    }
153  }  
154
155  public double getBlockWidth() {
156    return blockWidth;
157  }
158
159/**
160 * Used to initialise the spliced transcript renderer for
161 * a CDS feature where the ends of the feature define the
162 * frame of the feature.
163 */
164  public void startZiggy(StrandedFeature.Strand strand) {
165   // initialise state variables
166   System.out.println("startZiggy strand: " + strand);
167   zigPrecedesBlock = false;
168   phaseKnown = false;
169   this.strand = strand;
170  }
171
172/**
173 * This method is called to initialise the renderer for a
174 * spliced transcript.
175 *
176 * @param strand the strand that the transcipt is on.
177 * @param phase the initial translation phase of transcript coding region.
178 *
179 */
180  public void startZiggy(StrandedFeature.Strand strand, int phase) {
181   // initialise state variables
182//   System.out.println("startZiggy strand: " + strand);
183   zigPrecedesBlock = false;
184   phaseKnown = true;
185   moduloFrame = phase;
186   this.strand = strand;
187  }
188
189/**
190 * Render another "exon" in the correct translation frame.
191 *
192 */
193  public void renderLocation(
194           Graphics2D g,
195           SequenceRenderContext src,
196           Location loc) {
197
198    int minS = loc.getMin();
199    int maxS = loc.getMax();
200    double minP = src.sequenceToGraphics(minS);
201    double maxP = src.sequenceToGraphics(maxS);
202
203    // handle the zig
204    double midpointSeqCoord;
205    double terminalSeqCoord;
206    double terminalDrawCoord;
207    double midpointOffset;
208    double offset;
209
210    if (zigPrecedesBlock) {
211      // This is a continuation of the transcript.
212      // do the ziggy
213      // the apex is midway between blocks.
214//      System.out.println("renderLocation continued moduloFrame: " + moduloFrame  + " " + minS + " " + maxS); 
215        terminalSeqCoord = minP;
216
217      // current phase is previous phase corrected by intron-induced
218      // phase change.
219      moduloFrame = (moduloFrame + minS - 1 - zigOriginS)%3;
220      offset = frameOffset(moduloFrame, strand);
221
222      // zigs for forward frames go up, reverse go down.
223      if (strand == StrandedFeature.POSITIVE) {
224        midpointOffset = Math.min(offset, offsetPrevBlock);
225      }
226      else {
227        midpointOffset = Math.max(offset, offsetPrevBlock) + blockWidth;
228      }
229
230      terminalDrawCoord = offset + 0.5 * blockWidth;
231
232      Point2D.Double midpoint, terminal;
233      if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
234        // horizontal axis
235        midpointSeqCoord = 0.5*(zigOriginP.getX() + terminalSeqCoord);
236        midpoint = new Point2D.Double(midpointSeqCoord, midpointOffset);
237        terminal = new Point2D.Double(terminalSeqCoord, terminalDrawCoord);
238      }
239      else {
240        midpointSeqCoord = 0.5*(zigOriginP.getY() + terminalSeqCoord);
241        midpoint = new Point2D.Double(midpointOffset, midpointSeqCoord);
242        terminal = new Point2D.Double(terminalDrawCoord, terminalSeqCoord);
243      }
244
245      // draw ziggy
246      Paint prevPaint = g.getPaint();
247      g.setPaint(outline);
248      Line2D line = new Line2D.Double(zigOriginP, midpoint);
249      g.draw(line);
250      line = new Line2D.Double(midpoint, terminal);
251      g.draw(line);
252      g.setPaint(prevPaint);
253    }
254    else {
255      // this is first block, there is no zig yet.
256      // compute the frame.
257
258      if (!phaseKnown)
259          moduloFrame = minS%3;
260
261      // compute offset for frame to be drawn
262      offset = frameOffset(moduloFrame, strand);
263      zigPrecedesBlock = true;
264//      System.out.println("renderLocation 1st moduloFrame: " + moduloFrame + " " + minS + " " + maxS); 
265    }
266
267    // draw the block
268    Rectangle2D.Double block = new Rectangle2D.Double();
269    if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
270      block.setFrame(
271        minP, offset,
272        maxP-minP, blockWidth);
273    }
274    else {
275      block.setFrame(
276        offset, minP,
277        blockWidth, maxP-minP);
278    }
279    
280    if (fill != null) {
281      Paint prevPaint = g.getPaint();
282      g.setPaint(fill);
283      g.fill(block);
284      g.setPaint(prevPaint);
285    }
286
287    if (outline != null) {
288      Paint prevPaint = g.getPaint();
289      g.setPaint(outline);
290      g.draw(block);
291      g.setPaint(prevPaint);
292    }
293
294    // update origin for next ziggy
295    // origin of next ziggy on current block
296    double seqCoord;
297    double drawCoord = offset + 0.5 * blockWidth;
298
299    // all features arrive in sequence order irrespective of strand
300    // only the direction of zig changes.
301    seqCoord = maxP;  
302
303    // cache zig sequence origin and phase.
304    zigOriginS = maxS;
305    offsetPrevBlock = offset;
306  
307    if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
308      zigOriginP = new Point2D.Double(seqCoord, drawCoord);
309    }
310    else {
311      zigOriginP = new Point2D.Double(drawCoord, seqCoord);
312    }
313  }
314
315  public java.util.List sequenceExtentOfPixels(
316                          SequenceRenderContext src) {
317    // returns a list giving the extents represented by
318    // pixels in drawing area.
319    RangeLocation rangeS = src.getRange();
320    int minLocS = rangeS.getMin();
321    int maxLocS = rangeS.getMax();
322    int minP = (int) src.sequenceToGraphics(minLocS);
323    int maxP = (int) src.sequenceToGraphics(maxLocS);
324    // Vector to hold ranges for each pixel.  There are maxP-minP+1 pixels.
325    java.util.List extentList = new Vector(maxP - minP +1, 20);
326    for (int currP = minP; currP < maxP; currP++) {
327      // compute the extent of represented by each pixel
328      int minRange = Math.max((int) src.graphicsToSequence(currP), minLocS);
329      int maxRange = Math.min(((int) src.graphicsToSequence(currP + 1) - 1), maxLocS);
330      extentList.add(new RangeLocation(minRange, maxRange));
331    }
332    return extentList;
333  }
334
335/** 
336 * draws required bar in correct translation frame.
337 *
338 * @param base     the sequence coordinate of the bar.
339 * @param strand   strand that the bar annotates.
340 */
341  public void drawLine(
342                Graphics2D g,
343                SequenceRenderContext src,
344                int base, 
345                StrandedFeature.Strand strand) {
346
347    Paint prevPaint = g.getPaint();
348    g.setPaint(lines);
349
350    // compute the frame to use.
351    int moduloFrame = base%3;
352
353//    System.out.println("drawLine: base,strand,modulo" + base + " " + strand + " " + moduloFrame);
354    // get required offset for frame
355    double offset = frameOffset(moduloFrame, strand);
356
357    // compute position of line to be drawn
358    int lineP = (int) src.sequenceToGraphics(base);
359
360    // draw the line
361    if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
362      g.drawLine(lineP, (int) offset,
363                 lineP, (int) (offset + blockWidth));
364    }
365    else {
366      g.drawLine((int) offset, lineP,
367                 (int)(offset + blockWidth), lineP);
368    }
369    g.setPaint(prevPaint);
370  }
371
372  public void paint(
373    Graphics2D g,
374    SequenceRenderContext src) {
375    // this doesn't do anything
376  }
377
378  public SequenceViewerEvent processMouseEvent(
379    SequenceRenderContext src,
380    MouseEvent me,
381    List path
382  ) {
383    path.add(this);
384    int sPos = src.graphicsToSequence(me.getPoint());
385    return new SequenceViewerEvent(this, null, sPos, me, path);
386  }
387}
388