/* * 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.FontMetrics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.util.List; import org.biojava.bio.symbol.Location; /** *
RulerRenderer
renders numerical scales in sequence
* coordinates. The tick direction may be set to point upwards (or
* left when the scale is vertical) or downwards (right when the scale
* is vertical).
Note: The Compaq Java VMs 1.3.1 - 1.4.0 on Tru64 appear to have * a bug in font transformation which prevents a vertically oriented * ruler displaying correctly rotated text.
* * @author Matthew Pocock * @author Thomas Down * @author David Huen * @author Keith James * @author Kalle Näslund */ public class RulerRenderer implements SequenceRenderer { /** *TICKS_UP
indicates that the ticks will point
* upwards from a baseline.
*/
public static final int TICKS_UP = 0;
/**
* TICKS_DOWN
indicates that the ticks will point
* downwards from a baseline.
*/
public static final int TICKS_DOWN = 1;
private Line2D line;
private double depth;
private AffineTransform antiQuarter;
private int tickDirection;
private float tickHeight;
private float horizLabelOffset;
private float vertLabelOffset;
/**
* Creates a new RulerRenderer
with the default
* setting of ticks pointing downwards.
*/
public RulerRenderer() throws IllegalArgumentException
{
this(TICKS_DOWN);
}
/**
* Creates a new RulerRenderer
with the specified
* tick direction.
*
* @param tickDirection an int
.
* @exception IllegalArgumentException if an error occurs.
*/
public RulerRenderer(int tickDirection) throws IllegalArgumentException
{
line = new Line2D.Double();
antiQuarter = AffineTransform.getRotateInstance(Math.toRadians(-90));
if (tickDirection == TICKS_UP || tickDirection == TICKS_DOWN)
this.tickDirection = tickDirection;
else
throw new IllegalArgumentException("Tick direction may only be set to RulerRenderer.TICKS_UP or RulerRenderer.TICKS_DOWN");
depth = 20.0;
tickHeight = 4.0f;
horizLabelOffset = ((float) depth) - tickHeight - 2.0f;
vertLabelOffset = ((float) depth) - ((tickHeight + 2.0f) * 2.0f);
}
public double getMinimumLeader(SequenceRenderContext context)
{
return 0.0;
}
public double getMinimumTrailer(SequenceRenderContext context)
{
return 0.0;
}
public double getDepth(SequenceRenderContext src)
{
return depth + 1.0;
}
public void paint(Graphics2D g2, SequenceRenderContext context)
{
AffineTransform prevTransform = g2.getTransform();
g2.setPaint(Color.black);
Location visible = GUITools.getVisibleRange(context, g2);
if( visible == Location.empty ) {
return;
}
int min = visible.getMin();
int max = visible.getMax();
double minX = context.sequenceToGraphics(min);
double maxX = context.sequenceToGraphics(max);
double scale = context.getScale();
double halfScale = scale * 0.5;
if (context.getDirection() == SequenceRenderContext.HORIZONTAL)
{
if (tickDirection == TICKS_UP)
{
line.setLine(minX - halfScale, depth,
maxX + halfScale, depth);
}
else
{
line.setLine(minX - halfScale, 0.0,
maxX + halfScale, 0.0);
}
}
else
{
if (tickDirection == TICKS_UP)
{
line.setLine(depth, minX - halfScale,
depth, maxX + halfScale);
}
else
{
line.setLine(0.0, minX - halfScale,
0.0, maxX + halfScale);
}
}
g2.draw(line);
FontMetrics fMetrics = g2.getFontMetrics();
// The widest (== maxiumum) coordinate to draw
int coordWidth = fMetrics.stringWidth(Integer.toString(max));
// Minimum gap getween ticks
double minGap = (double) Math.max(coordWidth, 40);
// How many symbols does a gap represent?
int realSymsPerGap = (int) Math.ceil(((minGap + 5.0) / context.getScale()));
// We need to snap to a value beginning 1, 2 or 5.
double exponent = Math.floor(Math.log(realSymsPerGap) / Math.log(10));
double characteristic = realSymsPerGap / Math.pow(10.0, exponent);
int snapSymsPerGap;
if (characteristic > 5.0)
{
// Use unit ticks
snapSymsPerGap = (int) Math.pow(10.0, exponent + 1.0);
}
else if (characteristic > 2.0)
{
// Use ticks of 5
snapSymsPerGap = (int) (5.0 * Math.pow(10.0, exponent));
}
else
{
snapSymsPerGap = (int) (2.0 * Math.pow(10.0, exponent));
}
int minP = min + (snapSymsPerGap - min) % snapSymsPerGap;
for (int index = minP; index <= max; index += snapSymsPerGap)
{
double offset = context.sequenceToGraphics(index);
String labelString = String.valueOf(index);
float halfLabelWidth = fMetrics.stringWidth(labelString) / 2;
if (context.getDirection() == SequenceRenderContext.HORIZONTAL)
{
if (tickDirection == TICKS_UP)
{
line.setLine(offset + halfScale, depth - tickHeight,
offset + halfScale, depth);
g2.drawString(String.valueOf(index),
(float) (offset + halfScale - halfLabelWidth),
horizLabelOffset);
}
else
{
line.setLine(offset + halfScale, 0.0,
offset + halfScale, tickHeight);
g2.drawString(String.valueOf(index),
(float) (offset + halfScale - halfLabelWidth),
horizLabelOffset);
}
}
else
{
if (tickDirection == TICKS_UP)
{
line.setLine(depth, offset + halfScale,
depth - tickHeight, offset + halfScale);
g2.translate(vertLabelOffset,
offset + halfScale + halfLabelWidth);
g2.transform(antiQuarter);
g2.drawString(String.valueOf(index), 0.0f, 0.0f);
g2.setTransform(prevTransform);
}
else
{
line.setLine(0.0f, offset + halfScale,
tickHeight, offset + halfScale);
g2.translate(vertLabelOffset,
offset + halfScale + halfLabelWidth);
g2.transform(antiQuarter);
g2.drawString(String.valueOf(index), 0.0f, 0.0f);
g2.setTransform(prevTransform);
}
}
g2.draw(line);
}
}
public SequenceViewerEvent processMouseEvent(SequenceRenderContext context,
MouseEvent me,
List path)
{
path.add(this);
int sPos = context.graphicsToSequence(me.getPoint());
return new SequenceViewerEvent(this, null, sPos, me, path);
}
}