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.FontMetrics; 026import java.awt.Graphics2D; 027import java.awt.event.MouseEvent; 028import java.awt.geom.AffineTransform; 029import java.awt.geom.Line2D; 030import java.util.List; 031 032import org.biojava.bio.symbol.Location; 033 034/** 035 * <p><code>RulerRenderer</code> renders numerical scales in sequence 036 * coordinates. The tick direction may be set to point upwards (or 037 * left when the scale is vertical) or downwards (right when the scale 038 * is vertical).</p> 039 * 040 * <p>Note: The Compaq Java VMs 1.3.1 - 1.4.0 on Tru64 appear to have 041 * a bug in font transformation which prevents a vertically oriented 042 * ruler displaying correctly rotated text.</p> 043 * 044 * @author Matthew Pocock 045 * @author Thomas Down 046 * @author David Huen 047 * @author Keith James 048 * @author Kalle Näslund 049 */ 050public class RulerRenderer implements SequenceRenderer 051{ 052 /** 053 * <code>TICKS_UP</code> indicates that the ticks will point 054 * upwards from a baseline. 055 */ 056 public static final int TICKS_UP = 0; 057 /** 058 * <code>TICKS_DOWN</code> indicates that the ticks will point 059 * downwards from a baseline. 060 */ 061 public static final int TICKS_DOWN = 1; 062 063 private Line2D line; 064 private double depth; 065 private AffineTransform antiQuarter; 066 private int tickDirection; 067 private float tickHeight; 068 private float horizLabelOffset; 069 private float vertLabelOffset; 070 071 /** 072 * Creates a new <code>RulerRenderer</code> with the default 073 * setting of ticks pointing downwards. 074 */ 075 public RulerRenderer() throws IllegalArgumentException 076 { 077 this(TICKS_DOWN); 078 } 079 080 /** 081 * Creates a new <code>RulerRenderer</code> with the specified 082 * tick direction. 083 * 084 * @param tickDirection an <code>int</code>. 085 * @exception IllegalArgumentException if an error occurs. 086 */ 087 public RulerRenderer(int tickDirection) throws IllegalArgumentException 088 { 089 line = new Line2D.Double(); 090 antiQuarter = AffineTransform.getRotateInstance(Math.toRadians(-90)); 091 092 if (tickDirection == TICKS_UP || tickDirection == TICKS_DOWN) 093 this.tickDirection = tickDirection; 094 else 095 throw new IllegalArgumentException("Tick direction may only be set to RulerRenderer.TICKS_UP or RulerRenderer.TICKS_DOWN"); 096 097 depth = 20.0; 098 tickHeight = 4.0f; 099 100 horizLabelOffset = ((float) depth) - tickHeight - 2.0f; 101 vertLabelOffset = ((float) depth) - ((tickHeight + 2.0f) * 2.0f); 102 } 103 104 public double getMinimumLeader(SequenceRenderContext context) 105 { 106 return 0.0; 107 } 108 109 public double getMinimumTrailer(SequenceRenderContext context) 110 { 111 return 0.0; 112 } 113 114 public double getDepth(SequenceRenderContext src) 115 { 116 return depth + 1.0; 117 } 118 119 public void paint(Graphics2D g2, SequenceRenderContext context) 120 { 121 AffineTransform prevTransform = g2.getTransform(); 122 123 g2.setPaint(Color.black); 124 125 Location visible = GUITools.getVisibleRange(context, g2); 126 if( visible == Location.empty ) { 127 return; 128 } 129 130 int min = visible.getMin(); 131 int max = visible.getMax(); 132 double minX = context.sequenceToGraphics(min); 133 double maxX = context.sequenceToGraphics(max); 134 double scale = context.getScale(); 135 136 double halfScale = scale * 0.5; 137 138 if (context.getDirection() == SequenceRenderContext.HORIZONTAL) 139 { 140 if (tickDirection == TICKS_UP) 141 { 142 line.setLine(minX - halfScale, depth, 143 maxX + halfScale, depth); 144 } 145 else 146 { 147 line.setLine(minX - halfScale, 0.0, 148 maxX + halfScale, 0.0); 149 } 150 } 151 else 152 { 153 if (tickDirection == TICKS_UP) 154 { 155 line.setLine(depth, minX - halfScale, 156 depth, maxX + halfScale); 157 } 158 else 159 { 160 line.setLine(0.0, minX - halfScale, 161 0.0, maxX + halfScale); 162 } 163 } 164 165 g2.draw(line); 166 167 FontMetrics fMetrics = g2.getFontMetrics(); 168 169 // The widest (== maxiumum) coordinate to draw 170 int coordWidth = fMetrics.stringWidth(Integer.toString(max)); 171 172 // Minimum gap getween ticks 173 double minGap = (double) Math.max(coordWidth, 40); 174 175 // How many symbols does a gap represent? 176 int realSymsPerGap = (int) Math.ceil(((minGap + 5.0) / context.getScale())); 177 178 // We need to snap to a value beginning 1, 2 or 5. 179 double exponent = Math.floor(Math.log(realSymsPerGap) / Math.log(10)); 180 double characteristic = realSymsPerGap / Math.pow(10.0, exponent); 181 182 int snapSymsPerGap; 183 if (characteristic > 5.0) 184 { 185 // Use unit ticks 186 snapSymsPerGap = (int) Math.pow(10.0, exponent + 1.0); 187 } 188 else if (characteristic > 2.0) 189 { 190 // Use ticks of 5 191 snapSymsPerGap = (int) (5.0 * Math.pow(10.0, exponent)); 192 } 193 else 194 { 195 snapSymsPerGap = (int) (2.0 * Math.pow(10.0, exponent)); 196 } 197 198 int minP = min + (snapSymsPerGap - min) % snapSymsPerGap; 199 200 for (int index = minP; index <= max; index += snapSymsPerGap) 201 { 202 double offset = context.sequenceToGraphics(index); 203 String labelString = String.valueOf(index); 204 float halfLabelWidth = fMetrics.stringWidth(labelString) / 2; 205 206 if (context.getDirection() == SequenceRenderContext.HORIZONTAL) 207 { 208 if (tickDirection == TICKS_UP) 209 { 210 line.setLine(offset + halfScale, depth - tickHeight, 211 offset + halfScale, depth); 212 g2.drawString(String.valueOf(index), 213 (float) (offset + halfScale - halfLabelWidth), 214 horizLabelOffset); 215 } 216 else 217 { 218 line.setLine(offset + halfScale, 0.0, 219 offset + halfScale, tickHeight); 220 g2.drawString(String.valueOf(index), 221 (float) (offset + halfScale - halfLabelWidth), 222 horizLabelOffset); 223 } 224 } 225 else 226 { 227 if (tickDirection == TICKS_UP) 228 { 229 line.setLine(depth, offset + halfScale, 230 depth - tickHeight, offset + halfScale); 231 g2.translate(vertLabelOffset, 232 offset + halfScale + halfLabelWidth); 233 g2.transform(antiQuarter); 234 g2.drawString(String.valueOf(index), 0.0f, 0.0f); 235 g2.setTransform(prevTransform); 236 } 237 else 238 { 239 line.setLine(0.0f, offset + halfScale, 240 tickHeight, offset + halfScale); 241 g2.translate(vertLabelOffset, 242 offset + halfScale + halfLabelWidth); 243 g2.transform(antiQuarter); 244 g2.drawString(String.valueOf(index), 0.0f, 0.0f); 245 g2.setTransform(prevTransform); 246 } 247 } 248 g2.draw(line); 249 } 250 } 251 252 public SequenceViewerEvent processMouseEvent(SequenceRenderContext context, 253 MouseEvent me, 254 List path) 255 { 256 path.add(this); 257 int sPos = context.graphicsToSequence(me.getPoint()); 258 return new SequenceViewerEvent(this, null, sPos, me, path); 259 } 260}