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