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.Graphics2D; 025import java.awt.Rectangle; 026import java.awt.event.MouseEvent; 027import java.awt.geom.AffineTransform; 028import java.io.Serializable; 029import java.net.URL; 030 031import org.biojava.bio.seq.Feature; 032import org.biojava.bio.seq.FeatureHolder; 033import org.biojava.bio.seq.OptimizableFilter; 034import org.biojava.bio.symbol.Location; 035import org.biojava.utils.ChangeVetoException; 036import org.biojava.utils.net.URLFactory; 037 038/** 039 * <code>RectangularImapRenderer</code> is a decorator for 040 * <code>RectangularBeadRenderer</code> which adds the ability to 041 * create HTML image map coordinates which correspond to the feature 042 * rendering produced by the <code>RectangularBeadRenderer</code>. 043 * 044 * @author Keith James 045 * @since 1.3 046 */ 047public class RectangularImapRenderer 048 implements BeadFeatureRenderer, ImageMapRenderer, Serializable 049{ 050 private RectangularBeadRenderer renderer; 051 private ImageMap imageMap; 052 private URLFactory urlFactory; 053 054 /** 055 * Creates a new <code>RectangularImapRenderer</code>. 056 * 057 * @param renderer a <code>RectangularBeadRenderer</code>. 058 * @param imageMap an <code>ImageMap</code>. 059 * @param urlFactory a <code>URLFactory</code> which should be 060 * capable of creating a suitable URL from each 061 * <code>Feature</code> on the <code>Sequence</code> to be 062 * rendered. 063 */ 064 public RectangularImapRenderer(RectangularBeadRenderer renderer, 065 ImageMap imageMap, 066 URLFactory urlFactory) 067 { 068 this.renderer = renderer; 069 this.imageMap = imageMap; 070 this.urlFactory = urlFactory; 071 } 072 073 /** 074 * <code>getImageMap</code> returns the current image map. 075 * 076 * @return an <code>ImageMap</code>. 077 */ 078 public ImageMap getImageMap() 079 { 080 return imageMap; 081 } 082 083 /** 084 * <code>setImageMap</code> sets the current image map. 085 * 086 * @param imageMap an <code>ImageMap</code>. 087 */ 088 public void setImageMap(ImageMap imageMap) 089 { 090 this.imageMap = imageMap; 091 } 092 093 /** 094 * <code>setDelegateRenderer</code> for the specified filter. 095 * 096 * @param filter an <code>OptimizableFilter</code>. 097 * @param renderer a <code>BeadFeatureRenderer</code>. 098 */ 099 public void setDelegateRenderer(OptimizableFilter filter, 100 BeadFeatureRenderer renderer) 101 { 102 this.renderer.setDelegateRenderer(filter, renderer); 103 } 104 105 /** 106 * <p><code>renderImageMap</code> writes a set of image map 107 * coordinates corresponding to the rectangle drawn by the 108 * renderer. The hotspots created by this method have the rendered 109 * <code>Feature</code> set as their user object.</p> 110 * 111 * <p>This method is called by <code>renderFeature</code> when a 112 * raster image is rendered.</p> 113 * 114 * @param g2 a <code>Graphics2D</code>. 115 * @param f a <code>Feature</code>. 116 * @param context a <code>SequenceRenderContext</code>. 117 */ 118 public void renderImageMap(Graphics2D g2, 119 Feature f, 120 SequenceRenderContext context) 121 { 122 Rectangle bounds = g2.getDeviceConfiguration().getBounds(); 123 124 // Safe to cast as bounds come from raster 125 int mapWidth = (int) bounds.getWidth(); 126 int mapHeight = (int) bounds.getHeight(); 127 128 URL url = urlFactory.createURL(f); 129 130 double beadDepth = getBeadDepth(); 131 double beadDisplacement = getBeadDisplacement(); 132 boolean scaleHeight = getHeightScaling(); 133 134 AffineTransform t = g2.getTransform(); 135 double transX = t.getTranslateX(); 136 double transY = t.getTranslateY(); 137 138 int min, max, dif, x1, y1, x2, y2; 139 double posXW, posYN, width, height; 140 141 Location loc = f.getLocation(); 142 143 min = loc.getMin(); 144 max = loc.getMax(); 145 dif = max - min; 146 147 if (context.getDirection() == SequenceRenderContext.HORIZONTAL) 148 { 149 posXW = context.sequenceToGraphics(min); 150 posYN = beadDisplacement; 151 width = Math.max(((double) (dif + 1)) * context.getScale(), 1.0); 152 153 if (scaleHeight) 154 { 155 height = Math.min(beadDepth, width / 2.0); 156 157 // If the bead height occupies less than the full height 158 // of the renderer, move it down so that it is central 159 if (height < beadDepth) 160 posYN += ((beadDepth - height) / 2.0); 161 } 162 else 163 { 164 height = beadDepth; 165 } 166 } 167 else 168 { 169 posXW = beadDisplacement; 170 posYN = context.sequenceToGraphics(min); 171 height = Math.max(((double) dif + 1) * context.getScale(), 1.0); 172 173 if (scaleHeight) 174 { 175 width = Math.min(beadDepth, height / 2.0); 176 if (width < beadDepth) 177 posXW += ((beadDepth - width) / 2.0); 178 } 179 else 180 { 181 width = beadDepth; 182 } 183 } 184 185 // Apply translation and round 186 x1 = (int) Math.floor(posXW + transX); 187 y1 = (int) Math.floor(posYN + transY); 188 x2 = (int) Math.floor(posXW + width + transX); 189 y2 = (int) Math.floor(posYN + height + transY); 190 191 // If the whole rectangle is outside the image then ignore 192 // it 193 if (! (x1 > mapWidth || y1 > mapHeight || x2 < 0 || y2 < 0)) 194 { 195 x1 = Math.max(x1, 0); 196 y1 = Math.max(y1, 0); 197 x2 = Math.min(x2, mapWidth); 198 y2 = Math.min(y2, mapHeight); 199 200 Integer [] coordinates = new Integer[4]; 201 coordinates[0] = new Integer(x1); 202 coordinates[1] = new Integer(y1); 203 coordinates[2] = new Integer(x2); 204 coordinates[3] = new Integer(y2); 205 206 imageMap.addHotSpot(new ImageMap.HotSpot(ImageMap.RECT, 207 url, coordinates, f)); 208 } 209 } 210 211 public void renderFeature(Graphics2D g2, 212 Feature f, 213 SequenceRenderContext context) 214 { 215 renderImageMap(g2, f, context); 216 renderer.renderFeature(g2, f, context); 217 } 218 219 public void renderBead(Graphics2D g2, 220 Feature f, 221 SequenceRenderContext context) 222 { 223 renderer.renderBead(g2, f, context); 224 } 225 226 public double getDepth(SequenceRenderContext context) 227 { 228 return renderer.getDepth(context); 229 } 230 231 public double getBeadDepth() 232 { 233 return renderer.getBeadDepth(); 234 } 235 236 public double getBeadDisplacement() 237 { 238 return renderer.getBeadDisplacement(); 239 } 240 241 /** 242 * <code>getHeightScaling</code> returns the state of the height 243 * scaling policy. 244 * 245 * @return a <code>boolean</code> true if height scaling is 246 * enabled. 247 */ 248 public boolean getHeightScaling() 249 { 250 return renderer.getHeightScaling(); 251 } 252 253 /** 254 * <code>setHeightScaling</code> sets the height scaling 255 * policy. Default behaviour is for this to be enabled leading to 256 * features being drawn with a height equal to half their width, 257 * subject to a maximum height restriction equal to the 258 * <code>beadDepth</code> set in the constructor. If disabled, 259 * features will always be drawn at the maximum height allowed by 260 * the <code>beadDepth</code> parameter. 261 * 262 * @param isEnabled a <code>boolean</code>. 263 * 264 * @exception ChangeVetoException if an error occurs. 265 */ 266 public void setHeightScaling(boolean isEnabled) throws ChangeVetoException 267 { 268 renderer.setHeightScaling(isEnabled); 269 } 270 271 public FeatureHolder processMouseEvent(FeatureHolder holder, 272 SequenceRenderContext context, 273 MouseEvent mEvent) 274 { 275 return renderer.processMouseEvent(holder, context, mEvent); 276 } 277}