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.Shape; 028import java.awt.event.MouseEvent; 029import java.awt.geom.Area; 030import java.awt.geom.GeneralPath; 031import java.awt.geom.Rectangle2D; 032 033import org.biojava.bio.seq.Feature; 034import org.biojava.bio.seq.FeatureHolder; 035import org.biojava.bio.seq.StrandedFeature; 036import org.biojava.bio.symbol.Location; 037import org.biojava.utils.AbstractChangeable; 038import org.biojava.utils.ChangeEvent; 039import org.biojava.utils.ChangeSupport; 040import org.biojava.utils.ChangeType; 041import org.biojava.utils.ChangeVetoException; 042 043/** 044 * @author Matthew Pocock 045 * @author Keith James 046 * @author Thomas Down 047 */ 048public class BasicFeatureRenderer 049extends AbstractChangeable 050implements FeatureRenderer { 051 public static final ChangeType FILL = new ChangeType( 052 "The fill paint has changed", 053 "org.biojava.bio.gui.sequence.BasicFeatureRenderer", 054 "FILL", 055 SequenceRenderContext.REPAINT 056 ); 057 058 public static final ChangeType OUTLINE = new ChangeType( 059 "The outline paint has changed", 060 "org.biojava.bio.gui.sequence.BasicFeatureRenderer", 061 "OUTLINE", 062 SequenceRenderContext.REPAINT 063 ); 064 065 public static final ChangeType SIZE = new ChangeType( 066 "The size of the arrow has changed", 067 "org.biojava.bio.gui.sequence.BasicFeatureRenderer", 068 "SIZE", 069 SequenceRenderContext.LAYOUT 070 ); 071 072 public static final ChangeType SCOOP = new ChangeType( 073 "The scoop of the arrow has changed", 074 "org.biojava.bio.gui.sequence.BasicFeatureRenderer", 075 "SCOOP", 076 SequenceRenderContext.REPAINT 077 ); 078 079 private Paint fill; 080 private Paint outline; 081 private double arrowSize = 15.0; 082 private double arrowScoop = 4.0; 083 084 public BasicFeatureRenderer() { 085 fill = Color.red; 086 outline = Color.black; 087 } 088 089 public void setFill(Paint p) 090 throws ChangeVetoException { 091 if(hasListeners()) { 092 ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); 093 synchronized(cs) { 094 ChangeEvent ce = new ChangeEvent( 095 this, FILL, p, fill 096 ); 097 cs.firePreChangeEvent(ce); 098 fill = p; 099 cs.firePostChangeEvent(ce); 100 } 101 } else { 102 fill = p; 103 } 104 } 105 106 public Paint getFill() { 107 return fill; 108 } 109 110 public void setOutline(Paint p) 111 throws ChangeVetoException { 112 if(hasListeners()) { 113 ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); 114 synchronized(cs) { 115 ChangeEvent ce = new ChangeEvent( 116 this, OUTLINE, p, outline 117 ); 118 cs.firePreChangeEvent(ce); 119 outline = p; 120 cs.firePostChangeEvent(ce); 121 } 122 } else { 123 outline = p; 124 } 125 } 126 127 public Paint getOutline() { 128 return outline; 129 } 130 131 public void setArrowSize(double arrowSize) 132 throws ChangeVetoException { 133 if(hasListeners()) { 134 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 135 synchronized(cs) { 136 ChangeEvent ce = new ChangeEvent( 137 this, SequenceRenderContext.LAYOUT, 138 null, null, new ChangeEvent( 139 this, SIZE, new Double(this.arrowSize), new Double(arrowSize) 140 ) 141 ); 142 cs.firePreChangeEvent(ce); 143 this.arrowSize = arrowSize; 144 cs.firePostChangeEvent(ce); 145 } 146 } else { 147 this.arrowSize = arrowSize; 148 } 149 } 150 151 public double getArrowSize() { 152 return arrowSize; 153 } 154 155 public void setArrowScoop(double arrowScoop) 156 throws ChangeVetoException { 157 if(hasListeners()) { 158 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 159 synchronized(cs) { 160 ChangeEvent ce = new ChangeEvent( 161 this, SequenceRenderContext.LAYOUT, 162 null, null, new ChangeEvent( 163 this, SIZE, new Double(this.arrowSize), new Double(arrowSize) 164 ) 165 ); 166 cs.firePreChangeEvent(ce); 167 this.arrowScoop = arrowScoop; 168 cs.firePostChangeEvent(ce); 169 } 170 } else { 171 this.arrowScoop = arrowScoop; 172 } 173 } 174 175 public double getArrowScoop() { 176 return arrowScoop; 177 } 178 179 public void renderFeature( 180 Graphics2D g, 181 Feature f, 182 SequenceRenderContext src 183 ) { 184 Shape s = null; 185 Location loc = f.getLocation(); 186 float depth = (float) (arrowSize + 2.0 * arrowScoop); 187 188 double minD, maxD; 189 if (src.getScale() > 1.0) { 190 minD = src.sequenceToGraphics(loc.getMin()); 191 maxD = src.sequenceToGraphics(loc.getMax() + 1) - 1.0; 192 } else { 193 minD = src.sequenceToGraphics(loc.getMin()); 194 maxD = src.sequenceToGraphics(loc.getMax()); 195 } 196 float min = (float) minD; 197 float max = (float) maxD; 198 199 float minBounds = (float) src.sequenceToGraphics(src.getRange().getMin() - 1); 200 float maxBounds = (float) src.sequenceToGraphics(src.getRange().getMax() + 1); 201 Shape bounds; 202 if (src.getDirection() == SequenceRenderContext.HORIZONTAL) { 203 bounds = new Rectangle2D.Double(minBounds, 0, maxBounds - minBounds, depth); 204 } else { 205 bounds = new Rectangle2D.Double(0, minBounds, depth, maxBounds - minBounds); 206 } 207 208 // System.err.println("Drawing feature " + f.getType() + " min= " + min + " max=" + max); 209 210 211 if( (max - min) >= arrowSize) { 212 if (f instanceof StrandedFeature) { 213 StrandedFeature.Strand strand = ((StrandedFeature) f).getStrand(); 214 if(src.getDirection() == SequenceRenderContext.HORIZONTAL) { 215 float minY = 0.0f; 216 float maxY = depth; 217 float minYS = minY + (float) arrowScoop; 218 float maxYS = maxY - (float) arrowScoop; 219 float midY = (minY + maxY) * 0.5f; 220 float minX = min; 221 float maxX = max; 222 if(strand == StrandedFeature.POSITIVE) { 223 float midX = maxX - (float) arrowSize; 224 GeneralPath path = new GeneralPath(); 225 path.moveTo(minX, minYS); 226 path.lineTo(midX, minYS); 227 path.lineTo(midX, minY); 228 path.lineTo(maxX, midY); 229 path.lineTo(midX, maxY); 230 path.lineTo(midX, maxYS); 231 path.lineTo(minX, maxYS); 232 path.closePath(); 233 s = path; 234 } else if(strand == StrandedFeature.NEGATIVE) { 235 float midX = minX + (float) arrowSize; 236 GeneralPath path = new GeneralPath(); 237 path.moveTo(maxX, minYS); 238 path.lineTo(midX, minYS); 239 path.lineTo(midX, minY); 240 path.lineTo(minX, midY); 241 path.lineTo(midX, maxY); 242 path.lineTo(midX, maxYS); 243 path.lineTo(maxX, maxYS); 244 path.closePath(); 245 s = path; 246 } 247 } else { // vertical 248 float minX = 0.0f; 249 float maxX = depth; 250 float minXS = minX + (float) arrowScoop; 251 float maxXS = maxX - (float) arrowScoop; 252 float midX = (minX + maxX) * 0.5f; 253 float minY = min; 254 float maxY = max; 255 if(strand == StrandedFeature.POSITIVE) { 256 float midY = maxY - (float) arrowSize; 257 GeneralPath path = new GeneralPath(); 258 path.moveTo(minXS, minY); 259 path.lineTo(minXS, midY); 260 path.lineTo(minX, midY); 261 path.lineTo(midX, maxY); 262 path.lineTo(maxX, midY); 263 path.lineTo(maxXS, midY); 264 path.lineTo(maxXS, minY); 265 path.closePath(); 266 s = path; 267 } else if(strand == StrandedFeature.NEGATIVE) { 268 float midY = minY + (float) arrowSize; 269 GeneralPath path = new GeneralPath(); 270 path.moveTo(minXS, maxY); 271 path.lineTo(minXS, midY); 272 path.lineTo(minX, midY); 273 path.lineTo(midX, minY); 274 path.lineTo(maxX, midY); 275 path.lineTo(maxXS, midY); 276 path.lineTo(maxXS, maxY); 277 path.closePath(); 278 s = path; 279 } 280 } 281 } 282 } 283 if(s == null) { 284 if(src.getDirection() == SequenceRenderContext.HORIZONTAL) { 285 s = new Rectangle2D.Double(min, 0, Math.max(1.0, max-min), 2.0*arrowScoop + arrowSize); 286 } else { 287 s = new Rectangle2D.Double(0, min, 2.0*arrowScoop + arrowSize, Math.max(1.0, max-min)); 288 } 289 } 290 291 if (!bounds.contains(s.getBounds())) { 292 // System.err.println("Edgeclipping"); 293 294 s = new Area(s); 295 ((Area) s).intersect(new Area(bounds)); 296 } 297 298 if(fill != null) { 299 g.setPaint(fill); 300 g.fill(s); 301 } 302 if ( (outline != null) && ( (maxD - minD) > 4.0) ) { 303 g.setPaint(outline); 304 g.draw(s); 305 } else { 306 // System.err.println("Not drawing outline..."); 307 } 308 } 309 310 public double getDepth(SequenceRenderContext src) { 311 return arrowSize + 2.0 * arrowScoop + 1.0; 312 } 313 314 public FeatureHolder processMouseEvent( 315 FeatureHolder hits, 316 SequenceRenderContext src, 317 MouseEvent me 318 ) { 319 return hits; 320 } 321}