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.Rectangle2D; 030import java.io.Serializable; 031import java.util.Iterator; 032import java.util.List; 033 034import org.biojava.bio.BioError; 035import org.biojava.bio.seq.FeatureFilter; 036import org.biojava.bio.seq.FeatureHolder; 037import org.biojava.bio.seq.StrandedFeature.Strand; 038import org.biojava.bio.seq.homol.SimilarityPairFeature; 039import org.biojava.bio.symbol.Location; 040import org.biojava.bio.symbol.RangeLocation; 041import org.biojava.utils.AbstractChangeable; 042import org.biojava.utils.ChangeEvent; 043import org.biojava.utils.ChangeSupport; 044import org.biojava.utils.ChangeType; 045import org.biojava.utils.ChangeVetoException; 046 047/** 048 * <p><code>PairwiseDiagonalRenderer</code> renders a region of 049 * similarity between two sequences as a straight line. The effect 050 * produced is similar to a dotplot. This implementation requires that 051 * these regions be represented by 052 * <code>SimilarityPairFeature</code>s.<p> 053 * 054 * <p>Drawing outside the visible area using a range of valid 055 * <code>double</code>s may cause Java to hang (Sun JDK 1.3.1 on 056 * Linux, Compaq JDK 1.3.1 on Tru64, but not Sun JDK 1.4.0-beta2-b77 057 * on Linux). I got round this by manual clipping of the lines to the 058 * clip area. The code uses an implementation of the Cohen-Sutherland 059 * line-clipping algorithm which clips lines to within a 060 * rectangle.</p> 061 * 062 * <p>The clipping code is taken from Computer Graphics for Java 063 * Programmers by Leen Ammeraal (1998, ISBN 0-471-98142-7) and 064 * cosmetically altered to support Java2D objects. Any bugs introduced 065 * are my responsibility.</p> 066 * 067 * @author Keith James 068 * @author Leen Ammeraal 069 * @since 1.2 070 */ 071public class PairwiseDiagonalRenderer extends AbstractChangeable 072 implements PairwiseSequenceRenderer, Serializable 073{ 074 /** 075 * Constant <code>OUTLINE</code> indicating a change to the fill of 076 * the features. 077 */ 078 public static final ChangeType OUTLINE = 079 new ChangeType("The outline paint has changed", 080 "org.biojava.bio.gui.sequence.PairwiseDiagonalRenderer", 081 "OUTLINE", SequenceRenderContext.REPAINT); 082 083 /** 084 * <code>spf</code> is a filter which excludes all features except 085 * <code>SimilarityPairFeature</code>s. 086 */ 087 private static FeatureFilter spf; 088 089 static 090 { 091 String className = 092 "org.biojava.bio.seq.homol.SimilarityPairFeature"; 093 094 try 095 { 096 spf = new FeatureFilter.ByClass(Class.forName(className)); 097 } 098 catch (Exception e) 099 { 100 throw new BioError("Failed to load Class for " + className, e); 101 } 102 } 103 104 /** 105 * <code>line</code> is the line to be drawn for each feature. 106 */ 107 protected Line2D.Float line; 108 109 /** 110 * <code>outline</code> is the line colour. 111 */ 112 protected Paint outline; 113 114 /** 115 * Creates a new <code>PairwiseDiagonalRenderer</code> which will 116 * draw black lines. 117 */ 118 public PairwiseDiagonalRenderer() 119 { 120 this(Color.black); 121 } 122 123 /** 124 * Creates a new <code>PairwiseDiagonalRenderer</code> which will 125 * draw lines using the specified <code>Paint</code>. 126 * 127 * @param outline a <code>Paint</code>. 128 */ 129 public PairwiseDiagonalRenderer(Paint outline) 130 { 131 line = new Line2D.Float(); 132 this.outline = outline; 133 } 134 135 /** 136 * <code>paint</code> renders the feature as a simple line. 137 * 138 * @param g2 a <code>Graphics2D</code>. 139 * @param context a <code>PairwiseRenderContext</code>. 140 */ 141 public void paint(Graphics2D g2, PairwiseRenderContext context) 142 { 143 FeatureHolder fh; 144 145 if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) 146 { 147 fh = context.getFeatures().filter(new 148 FeatureFilter.And(new FeatureFilter.OverlapsLocation(context.getRange()), 149 spf), false); 150 } 151 else 152 { 153 fh = context.getFeatures().filter(new 154 FeatureFilter.And(new FeatureFilter.OverlapsLocation(context.getSecondaryRange()), 155 spf), false); 156 } 157 158 for (Iterator fi = fh.features(); fi.hasNext();) 159 { 160 SimilarityPairFeature f1 = (SimilarityPairFeature) fi.next(); 161 SimilarityPairFeature f2 = f1.getSibling(); 162 163 Strand s1 = f1.getStrand(); 164 Strand s2 = f2.getStrand(); 165 166 Location loc1 = f1.getLocation(); 167 Location loc2 = f2.getLocation(); 168 169 int min1 = loc1.getMin(); 170 int max1 = loc1.getMax(); 171 172 int min2 = loc2.getMin(); 173 int max2 = loc2.getMax(); 174 175 if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) 176 { 177 float posX1 = (float) context.sequenceToGraphics(min1); 178 float posY1 = (float) context.secondarySequenceToGraphics(min2); 179 float posX2 = (float) context.sequenceToGraphics(max1); 180 float posY2 = (float) context.secondarySequenceToGraphics(max2); 181 182 if (s1 == s2) 183 line.setLine(posX1, posY1, posX2, posY2); 184 else 185 line.setLine(posX2, posY1, posX1, posY2); 186 } 187 else 188 { 189 float posY1 = (float) context.sequenceToGraphics(min1); 190 float posX1 = (float) context.secondarySequenceToGraphics(min2); 191 float posY2 = (float) context.sequenceToGraphics(max1); 192 float posX2 = (float) context.secondarySequenceToGraphics(max2); 193 194 if (s1 == s2) 195 line.setLine(posX1, posY1, posX2, posY2); 196 else 197 line.setLine(posX2, posY1, posX1, posY2); 198 } 199 200 Rectangle2D clip = g2.getClipBounds(); 201 202 clipLine((float) clip.getMinX(), (float) clip.getMaxX(), 203 (float) clip.getMinY(), (float) clip.getMaxY(),line); 204 205 g2.setPaint(outline); 206 g2.draw(line); 207 } 208 } 209 210 /** 211 * <code>getOutline</code> returns the colour used to draw the 212 * lines. 213 * 214 * @return a <code>Paint</code>. 215 */ 216 public Paint getOutline() 217 { 218 return outline; 219 } 220 221 /** 222 * <code>setOutline</code> sets the the colour used to draw the 223 * lines. 224 * 225 * @param outline a <code>Paint</code>. 226 * 227 * @exception ChangeVetoException if an error occurs. 228 */ 229 public void setOutline(Paint outline) throws ChangeVetoException 230 { 231 if (hasListeners()) 232 { 233 ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); 234 synchronized(cs) 235 { 236 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT, 237 null, null, 238 new ChangeEvent(this, OUTLINE, 239 outline, 240 this.outline)); 241 cs.firePreChangeEvent(ce); 242 this.outline = outline; 243 cs.firePostChangeEvent(ce); 244 } 245 } 246 else 247 { 248 this.outline = outline; 249 } 250 } 251 252 /** 253 * <code>processMouseEvent</code> acts on a mouse gesture. The 254 * target object is a <code>FeatureHolder</code> containing the 255 * features on the primary sequence which contain the mouse 256 * pointer. 257 * 258 * @param context a <code>PairwiseRenderContext</code>. 259 * @param me a <code>MouseEvent</code>. 260 * @param path a <code>List</code>. 261 * 262 * @return a <code>SequenceViewerEvent</code>. 263 */ 264 public SequenceViewerEvent processMouseEvent(PairwiseRenderContext context, 265 MouseEvent me, 266 List path) 267 { 268 path.add(this); 269 270 double gPos; 271 272 if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) 273 gPos = me.getPoint().getX(); 274 else 275 gPos = me.getPoint().getY(); 276 277 int priMin = context.graphicsToSequence(gPos); 278 int priMax = context.graphicsToSequence(gPos + 1); 279 280 FeatureHolder fh = context.getFeatures().filter(new 281 FeatureFilter.And(new FeatureFilter.OverlapsLocation(new 282 RangeLocation(priMin, priMax)), spf), false); 283 284 return new SequenceViewerEvent(this, fh, priMin, me, path); 285 } 286 287 /** 288 * <code>clipLine</code> clips the line to within the rectangle. 289 * Cohen-Sutherland clipping implementation by Leen Ammeraal. 290 * 291 * @param xMin a <code>float</code>. 292 * @param xMax a <code>float</code>. 293 * @param yMin a <code>float</code>. 294 * @param yMax a <code>float</code>. 295 * @param line a <code>Line2D.Float</code>. 296 */ 297 private void clipLine(float xMin, float xMax, 298 float yMin, float yMax, Line2D.Float line) 299 { 300 int clipTypeX = calcClipType(xMin, xMax, 301 yMin, yMax, 302 line.x1, line.y1); 303 304 int clipTypeY = calcClipType(xMin, xMax, 305 yMin, yMax, 306 line.x2, line.y2); 307 308 float dx, dy; 309 310 if ((clipTypeX | clipTypeY) != 0) 311 { 312 if ((clipTypeX & clipTypeY) != 0) 313 return; 314 315 dx = line.x2 - line.x1; 316 dy = line.y2 - line.y1; 317 318 if (clipTypeX != 0) 319 { 320 if ((clipTypeX & 8) == 8) 321 { 322 line.y1 += (xMin - line.x1) * dy / dx; 323 line.x1 = xMin; 324 } 325 else if ((clipTypeX & 4) == 4) 326 { 327 line.y1 += (xMax - line.x1) * dy / dx; 328 line.x1 = xMax; 329 } 330 else if ((clipTypeX & 2) == 2) 331 { 332 line.x1 += (yMin - line.y1) * dx / dy; 333 line.y1 = yMin; 334 } 335 else if ((clipTypeX & 1) == 1) 336 { 337 line.x1 += (yMax - line.y1) * dx / dy; 338 line.y1 = yMax; 339 } 340 } 341 else if (clipTypeY != 0) 342 { 343 if ((clipTypeY & 8) == 8) 344 { 345 line.y2 += (xMin - line.x2) * dy / dx; 346 line.x2 = xMin; 347 } 348 else if ((clipTypeY & 4) == 4) 349 { 350 line.y2 += (xMax - line.x2) * dy / dx; 351 line.x2 = xMax; 352 } 353 else if ((clipTypeY & 2) == 2) 354 { 355 line.x2 += (yMin - line.y2) * dx / dy; 356 line.y2 = yMin; 357 } 358 else if ((clipTypeY & 1) == 1) 359 { 360 line.x2 += (yMax - line.y2) * dx / dy; 361 line.y2 = yMax; 362 } 363 } 364 } 365 } 366 367 /** 368 * <code>calcClipType</code> calculates which sort of clipping to 369 * carry out and returns the relevant code. 370 * 371 * @param xMin a <code>float</code>. 372 * @param xMax a <code>float</code>. 373 * @param yMin a <code>float</code>. 374 * @param yMax a <code>float</code>. 375 * @param x a <code>float</code>. 376 * @param y a <code>float</code>. 377 * 378 * @return an <code>int</code> code. 379 */ 380 private int calcClipType(float xMin, float xMax, 381 float yMin, float yMax, float x, float y) 382 { 383 return 384 (x < xMin ? 8 : 0) | 385 (x > xMax ? 4 : 0) | 386 (y < yMin ? 2 : 0) | 387 (y > yMax ? 1 : 0); 388 } 389}