/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.bio.gui.sequence; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.event.MouseEvent; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.Iterator; import java.util.List; import org.biojava.bio.BioError; import org.biojava.bio.seq.FeatureFilter; import org.biojava.bio.seq.FeatureHolder; import org.biojava.bio.seq.StrandedFeature.Strand; import org.biojava.bio.seq.homol.SimilarityPairFeature; import org.biojava.bio.symbol.Location; import org.biojava.bio.symbol.RangeLocation; import org.biojava.utils.AbstractChangeable; import org.biojava.utils.ChangeEvent; import org.biojava.utils.ChangeSupport; import org.biojava.utils.ChangeType; import org.biojava.utils.ChangeVetoException; /** *

PairwiseDiagonalRenderer renders a region of * similarity between two sequences as a straight line. The effect * produced is similar to a dotplot. This implementation requires that * these regions be represented by * SimilarityPairFeatures.

* *

Drawing outside the visible area using a range of valid * doubles may cause Java to hang (Sun JDK 1.3.1 on * Linux, Compaq JDK 1.3.1 on Tru64, but not Sun JDK 1.4.0-beta2-b77 * on Linux). I got round this by manual clipping of the lines to the * clip area. The code uses an implementation of the Cohen-Sutherland * line-clipping algorithm which clips lines to within a * rectangle.

* *

The clipping code is taken from Computer Graphics for Java * Programmers by Leen Ammeraal (1998, ISBN 0-471-98142-7) and * cosmetically altered to support Java2D objects. Any bugs introduced * are my responsibility.

* * @author Keith James * @author Leen Ammeraal * @since 1.2 */ public class PairwiseDiagonalRenderer extends AbstractChangeable implements PairwiseSequenceRenderer, Serializable { /** * Constant OUTLINE indicating a change to the fill of * the features. */ public static final ChangeType OUTLINE = new ChangeType("The outline paint has changed", "org.biojava.bio.gui.sequence.PairwiseDiagonalRenderer", "OUTLINE", SequenceRenderContext.REPAINT); /** * spf is a filter which excludes all features except * SimilarityPairFeatures. */ private static FeatureFilter spf; static { String className = "org.biojava.bio.seq.homol.SimilarityPairFeature"; try { spf = new FeatureFilter.ByClass(Class.forName(className)); } catch (Exception e) { throw new BioError("Failed to load Class for " + className, e); } } /** * line is the line to be drawn for each feature. */ protected Line2D.Float line; /** * outline is the line colour. */ protected Paint outline; /** * Creates a new PairwiseDiagonalRenderer which will * draw black lines. */ public PairwiseDiagonalRenderer() { this(Color.black); } /** * Creates a new PairwiseDiagonalRenderer which will * draw lines using the specified Paint. * * @param outline a Paint. */ public PairwiseDiagonalRenderer(Paint outline) { line = new Line2D.Float(); this.outline = outline; } /** * paint renders the feature as a simple line. * * @param g2 a Graphics2D. * @param context a PairwiseRenderContext. */ public void paint(Graphics2D g2, PairwiseRenderContext context) { FeatureHolder fh; if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) { fh = context.getFeatures().filter(new FeatureFilter.And(new FeatureFilter.OverlapsLocation(context.getRange()), spf), false); } else { fh = context.getFeatures().filter(new FeatureFilter.And(new FeatureFilter.OverlapsLocation(context.getSecondaryRange()), spf), false); } for (Iterator fi = fh.features(); fi.hasNext();) { SimilarityPairFeature f1 = (SimilarityPairFeature) fi.next(); SimilarityPairFeature f2 = f1.getSibling(); Strand s1 = f1.getStrand(); Strand s2 = f2.getStrand(); Location loc1 = f1.getLocation(); Location loc2 = f2.getLocation(); int min1 = loc1.getMin(); int max1 = loc1.getMax(); int min2 = loc2.getMin(); int max2 = loc2.getMax(); if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) { float posX1 = (float) context.sequenceToGraphics(min1); float posY1 = (float) context.secondarySequenceToGraphics(min2); float posX2 = (float) context.sequenceToGraphics(max1); float posY2 = (float) context.secondarySequenceToGraphics(max2); if (s1 == s2) line.setLine(posX1, posY1, posX2, posY2); else line.setLine(posX2, posY1, posX1, posY2); } else { float posY1 = (float) context.sequenceToGraphics(min1); float posX1 = (float) context.secondarySequenceToGraphics(min2); float posY2 = (float) context.sequenceToGraphics(max1); float posX2 = (float) context.secondarySequenceToGraphics(max2); if (s1 == s2) line.setLine(posX1, posY1, posX2, posY2); else line.setLine(posX2, posY1, posX1, posY2); } Rectangle2D clip = g2.getClipBounds(); clipLine((float) clip.getMinX(), (float) clip.getMaxX(), (float) clip.getMinY(), (float) clip.getMaxY(),line); g2.setPaint(outline); g2.draw(line); } } /** * getOutline returns the colour used to draw the * lines. * * @return a Paint. */ public Paint getOutline() { return outline; } /** * setOutline sets the the colour used to draw the * lines. * * @param outline a Paint. * * @exception ChangeVetoException if an error occurs. */ public void setOutline(Paint outline) throws ChangeVetoException { if (hasListeners()) { ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); synchronized(cs) { ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT, null, null, new ChangeEvent(this, OUTLINE, outline, this.outline)); cs.firePreChangeEvent(ce); this.outline = outline; cs.firePostChangeEvent(ce); } } else { this.outline = outline; } } /** * processMouseEvent acts on a mouse gesture. The * target object is a FeatureHolder containing the * features on the primary sequence which contain the mouse * pointer. * * @param context a PairwiseRenderContext. * @param me a MouseEvent. * @param path a List. * * @return a SequenceViewerEvent. */ public SequenceViewerEvent processMouseEvent(PairwiseRenderContext context, MouseEvent me, List path) { path.add(this); double gPos; if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) gPos = me.getPoint().getX(); else gPos = me.getPoint().getY(); int priMin = context.graphicsToSequence(gPos); int priMax = context.graphicsToSequence(gPos + 1); FeatureHolder fh = context.getFeatures().filter(new FeatureFilter.And(new FeatureFilter.OverlapsLocation(new RangeLocation(priMin, priMax)), spf), false); return new SequenceViewerEvent(this, fh, priMin, me, path); } /** * clipLine clips the line to within the rectangle. * Cohen-Sutherland clipping implementation by Leen Ammeraal. * * @param xMin a float. * @param xMax a float. * @param yMin a float. * @param yMax a float. * @param line a Line2D.Float. */ private void clipLine(float xMin, float xMax, float yMin, float yMax, Line2D.Float line) { int clipTypeX = calcClipType(xMin, xMax, yMin, yMax, line.x1, line.y1); int clipTypeY = calcClipType(xMin, xMax, yMin, yMax, line.x2, line.y2); float dx, dy; if ((clipTypeX | clipTypeY) != 0) { if ((clipTypeX & clipTypeY) != 0) return; dx = line.x2 - line.x1; dy = line.y2 - line.y1; if (clipTypeX != 0) { if ((clipTypeX & 8) == 8) { line.y1 += (xMin - line.x1) * dy / dx; line.x1 = xMin; } else if ((clipTypeX & 4) == 4) { line.y1 += (xMax - line.x1) * dy / dx; line.x1 = xMax; } else if ((clipTypeX & 2) == 2) { line.x1 += (yMin - line.y1) * dx / dy; line.y1 = yMin; } else if ((clipTypeX & 1) == 1) { line.x1 += (yMax - line.y1) * dx / dy; line.y1 = yMax; } } else if (clipTypeY != 0) { if ((clipTypeY & 8) == 8) { line.y2 += (xMin - line.x2) * dy / dx; line.x2 = xMin; } else if ((clipTypeY & 4) == 4) { line.y2 += (xMax - line.x2) * dy / dx; line.x2 = xMax; } else if ((clipTypeY & 2) == 2) { line.x2 += (yMin - line.y2) * dx / dy; line.y2 = yMin; } else if ((clipTypeY & 1) == 1) { line.x2 += (yMax - line.y2) * dx / dy; line.y2 = yMax; } } } } /** * calcClipType calculates which sort of clipping to * carry out and returns the relevant code. * * @param xMin a float. * @param xMax a float. * @param yMin a float. * @param yMax a float. * @param x a float. * @param y a float. * * @return an int code. */ private int calcClipType(float xMin, float xMax, float yMin, float yMax, float x, float y) { return (x < xMin ? 8 : 0) | (x > xMax ? 4 : 0) | (y < yMin ? 2 : 0) | (y > yMax ? 1 : 0); } }