//package org.genedb.web.gui;
//
//import org.genedb.db.domain.objects.CompoundLocatedFeature;
//import org.genedb.db.domain.objects.Gap;
//import org.genedb.db.domain.objects.LocatedFeature;
//import org.genedb.web.mvc.model.BerkeleyMapFactory;
//
//import org.gmod.schema.feature.Region;
//
//import org.apache.log4j.Logger;
//
//import net.sf.json.JSONString;
//
//import java.awt.AlphaComposite;
//import java.awt.Color;
//import java.awt.Font;
//import java.awt.Graphics2D;
//import java.awt.RenderingHints;
//import java.awt.font.FontRenderContext;
//import java.awt.font.LineMetrics;
//import java.awt.geom.Rectangle2D;
//import java.awt.image.BufferedImage;
//import java.awt.image.IndexColorModel;
//import java.io.ByteArrayOutputStream;
//import java.io.IOException;
//import java.io.OutputStream;
//import java.util.ArrayList;
//import java.util.Collection;
//import java.util.List;
//import java.util.Map;
//import java.util.NavigableMap;
//import java.util.SortedSet;
//import java.util.TreeMap;
//
//import javax.imageio.ImageIO;
//
//public abstract class RenderedDiagram {
// private static final Logger logger = Logger.getLogger(RenderedDiagram.class);
//
// private static final int MAX_HEIGHT=5000;
//
// protected static final String FILE_FORMAT = "png";
// protected static final String FILE_EXT = "png";
// protected boolean thumbNailMode;
//
// private BerkeleyMapFactory bmf;
//
// public void setBerkelyMapFactory(BerkeleyMapFactory bmf) {
// this.bmf = bmf;
// }
//
// protected enum ColorModel { DIRECT, INDEXED }
//
// public class Tile implements JSONString {
// private int width;
// private String key;
//
// public Tile(int width, String key) {
// this.width = width;
// this.key = key;
// }
//
// public String toJSONString() {
// return String.format("[%d,\"%s\"]", width, key);
// }
// }
//
// // Configuration members
//
// /**
// * What color model should be used?
// */
// protected ColorModel colorModel = ColorModel.INDEXED;
//
// /*
// * These are used for the label buffer, when the INDEXED color model is in use.
// */
// private static final int MAX_LABEL_WIDTH = 100;
// private static final int MAX_LABEL_HEIGHT = 50;
//
// /**
// * The scale at which this diagram is drawn, in bases per pixel.
// */
// protected double basesPerPixel = 10.0;
//
// /**
// * The horizontal distance in pixels from where the axis break slash crosses the
// * axis to its farthest extent above-right or below-left.
// */
// private int axisBreakX = 2;
//
// /**
// * The vertical distance in pixels from where the axis break slash crosses the
// * axis to its farthest extent above-right or below-left.
// */
// private int axisBreakY = 4;
//
// /**
// * The horizontal distance in pixels between the two slashes that denote
// * a break in the axis.
// */
// private int axisBreakGap = 6;
//
// /**
// * The height of the scale track.
// */
// private int scaleTrackHeight = 22;
//
// /**
// * The height of an ordinary track, in pixels.
// */
// private int trackHeight = 20;
//
// /**
// * The width, in pixels, of the left-hand margin.
// */
// private int leftMargin = 0;
//
// /**
// * The width, in pixels, of the right-hand margin.
// */
// private int rightMargin = 0;
//
// /**
// * The colour of the scale.
// */
// private Color scaleColor = Color.GRAY;
//
// /**
// * The colour of the labels.
// */
// private Color labelColor = Color.BLACK;
//
// /**
// * The colour of the label background. If <code>null</code>, no label background is printed.
// * (Note that LCD text antialiasing doesn't work on a transparent background. Also note
// * that IE6 does not ordinarily render partial transparency, though there is a workaround
// * using AlphaImageLoader.)
// */
// private Color labelBackgroundColor = null;
//
// /**
// * Font used for printing figures on the scale track
// */
// private Font labelFont;
//
//
// /**
// * Distance between minor scale ticks, in bases
// */
// private int minorTickDistance = 200;
//
// /**
// * Distance between major scale ticks, in bases.
// * Must be a multiple of <code>MINOR_TICK_DISTANCE</code>.
// */
// private int majorTickDistance = 1000;
//
// /**
// * Height of each minor scale tick above the axis, in pixels
// */
// private int minorTickHeightAbove = 2;
//
// /**
// * Height of each minor scale tick below the axis, in pixels
// */
// private int minorTickHeightBelow = 2;
//
// /**
// * Height of each major scale tick above the axis, in pixels
// */
// private int majorTickHeightAbove = 4;
//
// /**
// * Height of each major scale tick below the axis, in pixels
// */
// private int majorTickHeightBelow = 4;
//
// /**
// * Height of the scale boundary markers, above and below, in pixels.
// */
// private int boundaryTickHeight = 6;
//
// /**
// * Vertical position of scale axis within scale track,
// * measured in pixels downwards from the top of the scale track.
// */
// private int scaleVerticalPos = majorTickHeightAbove;
//
// /**
// * How much space to leave between a major scale tick and the label below, in pixels.
// */
// private static final int labelSep = 2;
//
//
// // Protected members
// protected Graphics2D graf;
//
//
// // Private members. Should be considered as implementation details.
//
// private Graphics2D labelGraf;
//
// private int numberOfPositiveTracks;
// private int numberOfNegativeTracks;
//
// protected TrackedDiagram diagram;
// private NavigableMap<Integer,Gap> gapsByStart = new TreeMap<Integer,Gap>();
// private int start;
// private int end;
// private int sizeExcludingGaps;
//
// private BufferedImage image;
// private BufferedImage labelBuffer;
//
// protected RenderedDiagram(TrackedDiagram diagram) {
// this.diagram = diagram;
// this.start = diagram.getStart();
// this.end = diagram.getEnd();
//
// this.numberOfPositiveTracks = diagram.numberOfPositiveTracks();
// this.numberOfNegativeTracks = diagram.numberOfNegativeTracks();
// }
//
// protected void addGap(Gap gap) {
// gapsByStart.put(gap.getFmin(), gap);
// }
//
// protected Iterable<Gap> getGaps() {
// return gapsByStart.values();
// }
//
// /**
// * Compute the size, excluding gaps, between
// * <code>start</code> and <code>end</code>,
// * and store the result in <code>sizeExcludingGaps</code>.
// */
// protected void calculateSizeExcludingGaps() {
// sizeExcludingGaps = 0;
// int loc = getStart();
// for (Gap gap : gapsByStart.values()) {
// if (loc < gap.getFmin()) {
// sizeExcludingGaps += gap.getFmin() - loc;
// loc = gap.getFmax();
// }
// }
//
// if (loc < getEnd())
// sizeExcludingGaps += getEnd() - loc;
// }
//
// public RenderedDiagram restrict(int start, int end) {
// if (start < diagram.getStart()) {
// logger.warn(String.format("Start of diagram is %d, start of restriction is %d",
// diagram.getStart(), start));
// this.start = diagram.getStart();
// }
// else
// this.start = start;
//
// if (end > diagram.getEnd()) {
// logger.warn(String.format("End of diagram is %d, end of restriction is %d",
// diagram.getEnd(), end));
// this.end = diagram.getEnd();
// }
// else
// this.end = end;
//
// return this;
// }
//
// abstract String getKey();
//
//
// /**
// * Get the location of the start of the diagram.
// *
// * @return the start of the diagram, in interbase coordinates
// */
// public int getStart() {
// return start;
// }
//
// /**
// * Get the location of the end of the diagram.
// *
// * @return the end of the diagram, in interbase coordinates
// */
// public int getEnd() {
// return end;
// }
//
//
//// public abstract String getPreferredFilename();
//
//// private String getPreferredFilenameForTile(int tileIndex, int tileWidth) {
//// return String.format("%s%09d-%09ds%.0ft%dw%d.%s", filenamePrefix, getStart(), getEnd(),
//// getBasesPerPixel(), tileIndex, tileWidth, FILE_EXT);
//// }
//
// public TrackedDiagram getDiagram() {
// return this.diagram;
// }
//
// /**
// * Configure this diagram to render as a thumbnail.
// *
// * @param maxWidth the maximum width, in pixels, of the rendered thumbnail
// * @return this object
// */
// public RenderedDiagram asThumbnail(int maxWidth) {
// setMaxWidth(maxWidth, 0);
// setScaleTrackHeight(1);
// setTrackHeight(2);
// setAxisBreakSlash(0, 0);
// thumbNailMode=true;
// forceTracks(2, 2);
//
// /* For thumbnails, the resulting file is usually smaller with a direct color model.
// * This means we have to apply the AlphaImageLoader hack to the chromosome thumbnail
// * for IE6. */
// this.colorModel = ColorModel.DIRECT;
//
// return this;
// }
//
// /**
// * Get the width in pixels
// * @return the width in pixels of this rendered diagram
// */
// public int getWidth() {
// return leftMargin + pixelWidth(getStart(), getEnd()) + rightMargin;
// }
//
// /**
// * Constrain the diagram to fit within a fixed width,
// * for a given gap size, by adjusting the scale. The
// * resulting width will be as close as possible to maxWidth,
// * but no larger. The <code>axisBreakGap</code> is also
// * set equal to the supplied value.
// *
// * @param maxWidth the maximum allowed width, in pixels
// * @param axisBreakGap the size, in pixels, of a break in the axis
// * representing a gap.
// * @return the actual width of the diagram
// */
// public int setMaxWidth(int maxWidth, int axisBreakGap) {
// this.axisBreakGap = axisBreakGap;
// calculateSizeExcludingGaps();
//
// int maxWidthExcludingGaps = maxWidth - axisBreakGap * this.gapsByStart.size();
// setBasesPerPixel(sizeExcludingGaps, maxWidthExcludingGaps);
//
// calculateContigs();
// assert getWidth() <= maxWidth;
// return getWidth();
// }
//
// /**
// * Get the height in pixels
// * @return the height in pixels of this rendered diagram
// */
// public int getHeight() {
// return scaleTrackHeight + numberOfTracks() * trackHeight;
// }
//
// /**
// * How many tracks will this diagram have, when rendered?
// * May be different from getDiagram().numberOfTracks(),
// * if {@link #forceTracks(int,int)} has been used.
// *
// * @return
// */
// private int numberOfTracks() {
// return numberOfPositiveTracks + numberOfNegativeTracks;
// }
//
// /**
// * Force the rendered diagram to have the specified number of tracks.
// * Thus there may be empty tracks in the rendered diagram,
// * and higher-numbered tracks will not be shown.
// *
// * @param positiveTracks
// * @param negativeTracks
// */
// private void forceTracks(int positiveTracks, int negativeTracks) {
// this.numberOfPositiveTracks = positiveTracks;
// this.numberOfNegativeTracks = negativeTracks;
// }
//
// /**
// * Get the height of the ordinary tracks of this diagram.
// * @return the height in pixels
// */
// public int getTrackHeight() {
// return trackHeight;
// }
//
// /**
// * Set the height of the ordinary tracks of this diagram.
// * @param trackHeight the height in pixels
// */
// public void setTrackHeight(int trackHeight) {
// this.trackHeight = trackHeight;
// }
//
// /**
// * Get the height of the scale track of this diagram.
// * @return the height in pixels
// */
// public int getScaleTrackHeight() {
// return scaleTrackHeight;
// }
//
// /**
// * Set the height of the scale track.
// * @param scaleTrackHeight the height in pixels
// */
// public void setScaleTrackHeight(int scaleTrackHeight) {
// this.scaleTrackHeight = scaleTrackHeight;
// }
//
// protected void setAxisBreakSlash(int axisBreakX, int axisBreakY) {
// this.axisBreakX = axisBreakX;
// this.axisBreakY = axisBreakY;
// }
//
// protected void setAxisBreakGap(int axisBreakGap) {
// this.axisBreakGap = axisBreakGap;
// }
//
// protected int getMinorTickHeightAbove() {
// return minorTickHeightAbove;
// }
//
// protected void setMinorTickHeightAbove(int minorTickHeightAbove) {
// this.minorTickHeightAbove = minorTickHeightAbove;
// }
//
// protected int getMinorTickHeightBelow() {
// return minorTickHeightBelow;
// }
//
// protected void setMinorTickHeightBelow(int minorTickHeightBelow) {
// this.minorTickHeightBelow = minorTickHeightBelow;
// }
//
// protected int getMajorTickHeightAbove() {
// return majorTickHeightAbove;
// }
//
// protected void setMajorTickHeightAbove(int majorTickHeightAbove) {
// this.majorTickHeightAbove = majorTickHeightAbove;
// }
//
// protected int getMajorTickHeightBelow() {
// return majorTickHeightBelow;
// }
//
// protected void setMajorTickHeightBelow(int majorTickHeightBelow) {
// this.majorTickHeightBelow = majorTickHeightBelow;
// }
//
// protected void setBoundaryTickHeight(int boundaryTickHeight) {
// this.boundaryTickHeight = boundaryTickHeight;
// }
//
// //public abstract String getRelativeRenderDirectory();
//
// public void setLabelFont(Font labelFont) {
// this.labelFont = labelFont;
// }
//
// abstract public String getKeyForTile(int index, int start, int width);
//
// public List<RenderedContextMap.Tile> renderTiles(int tileWidth) throws ImageCreationException {
//
// beforeRender();
// drawScaleTrack();
// renderFeatures();
//
// List<RenderedContextMap.Tile> tiles = new ArrayList<RenderedContextMap.Tile>();
//
// if (getWidth() != image.getWidth())
// throw new IllegalStateException(String.format(
// "Image width: expected=%d, actual=%d", getWidth(), image.getWidth()));
//
// for (int startOfTile = 0, tileIndex = 1; startOfTile < getWidth(); startOfTile += tileWidth, tileIndex++) {
//
// int widthOfThisTile = tileWidth;
// if (startOfTile + widthOfThisTile > getWidth()) {
// widthOfThisTile = getWidth() - startOfTile;
// }
//
// String key = getKeyForTile(tileIndex, startOfTile, tileWidth);
//
// logger.debug(String.format("Rendering tile %d (start=%d,width=%d) to '%s'", tileIndex, startOfTile, widthOfThisTile, key));
// BufferedImage tile = image.getSubimage(startOfTile, 0, widthOfThisTile, image.getHeight());
//
// try {
// ByteArrayOutputStream out = new ByteArrayOutputStream();
// ImageIO.write(tile, FILE_FORMAT, out);
// out.close();
// bmf.getImageMap().put(key, out.toByteArray());
// } catch (IOException exp) {
// throw new RuntimeException(String.format("Failed to write image file '%s'", key), exp);
// }
//
//
// tiles.add(new Tile(widthOfThisTile, key));
// }
//
// return tiles;
// }
//
// /**
// * A navigable map from base -> pixel, with an entry corresponding to
// * the beginning of each contiguous region. It is populated at the start
// * of rendering, by {@link #drawScaleLine()}, and used for the duration
// * of the rendering process.
// */
// private NavigableMap<Integer,Double> contigs;
//
// private static final Color TRANSPARENT = new Color(0,0,0,0);
//
// private void calculateContigs() {
// contigs = new TreeMap<Integer,Double>();
// double x = xCoordinate(getStart());
// for (Gap gap: gapsByStart.values()) {
// x = xCoordinateAsDoubleIgnoringMargin(gap.getFmin()) + axisBreakGap;
// contigs.put(gap.getFmax(), x);
// }
// }
//
// /**
// * The IndexColorModel to use in INDEXED mode.
// * Subclasses should override this method to return
// * an 8-bit <code>IndexColorModel</code> adequate to
// * represent all the colours used on the rendered
// * diagram.
// *
// * @return An 8-bit <code>IndexColorModel</code>
// */
// protected abstract IndexColorModel byteIndexedColorModel();
//
// protected void beforeRender() throws ImageCreationException {
// calculateContigs();
// if (getHeight() > MAX_HEIGHT) {
// // Abort
// image = null;
// throw new ImageCreationException(String.format("Height requested '%d' is unreasonable", getHeight()));
// }
// switch (colorModel) {
// case DIRECT:
// image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
// labelBuffer = null;
// break;
// case INDEXED:
// logger.info(String.format("About to try and create a BufferedImage of '%d' x '%d'", getWidth(), getHeight()));
// image = new BufferedImage(getWidth(), getHeight(),
// BufferedImage.TYPE_BYTE_INDEXED, byteIndexedColorModel());
// break;
// }
// graf = (Graphics2D) image.getGraphics();
//
// if (colorModel == ColorModel.INDEXED) {
// labelBuffer = new BufferedImage(MAX_LABEL_WIDTH, MAX_LABEL_HEIGHT, BufferedImage.TYPE_INT_ARGB_PRE);
// labelGraf = (Graphics2D) labelBuffer.getGraphics();
// labelGraf.setComposite(AlphaComposite.Src);
// setLabelRenderingHints(labelGraf);
// labelGraf.setFont(labelFont);
// }
// }
//
// protected void afterRender() {
// image = null;
// graf.dispose();
// graf = null;
// }
//
// public void writeTo(OutputStream out) throws ImageCreationException, IOException {
//
// logger.debug(String.format("Drawing diagram (%s) with dimensions %dx%d", getClass(), getWidth(), getHeight()));
//
// beforeRender();
// drawScaleTrack();
// renderFeatures();
//
// ImageIO.write(image, FILE_FORMAT, out);
//
// afterRender();
// }
//
// protected void renderFeatures() {
// SortedSet<AllocatedCompoundFeature> features = diagram.getAllocatedCompoundFeatures();
// for (AllocatedCompoundFeature superfeature : features) {
// drawCompoundFeature(superfeature);
// int track = superfeature.getTrack();
// CompoundLocatedFeature compoundFeature = superfeature.getFeature();
//
// if (compoundFeature.getFmax() < this.getStart() || compoundFeature.getFmin() > this.getEnd())
// continue;
//
// for (Collection<LocatedFeature> subfeatures: superfeature.getSubtracks()) {
// for (LocatedFeature subfeature: subfeatures) {
// drawFeature(track, subfeature, superfeature);
// }
// track += Integer.signum(track);
// }
// }
// }
//
// /**
// * This method is called to render a compound feature,
// * before rendering its individual subfeatures. The
// * default implementation does nothing; subclasses may
// * override it for their own purposes.
// *
// * @param allocatedCompoundFeature the allocated compound feature to draw
// */
// protected void drawCompoundFeature(AllocatedCompoundFeature allocatedCompoundFeature) {
// // empty
// }
//
// abstract protected void drawFeature(int track, LocatedFeature subfeature, AllocatedCompoundFeature superfeature);
//
// /**
// * Get the scale at which this diagram is drawn, in bases per pixel.
// *
// * @return
// */
// public double getBasesPerPixel() {
// return basesPerPixel;
// }
//
// /**
// * Set the scale at which this diagram should be drawn. Will adjust the
// * width appropriately.
// *
// * @param basesPerPixel the new scale, in bases per pixel
// */
// public void setBasesPerPixel(int basesPerPixel) {
// if (basesPerPixel <= 0)
// throw new IllegalArgumentException(String.format("Cannot have %d bases per pixel!",
// basesPerPixel));
//
// this.basesPerPixel = basesPerPixel;
// }
//
// public void setBasesPerPixel(int bases, int pixels) {
// logger.debug(String.format("Setting bases per pixel for bases=%d, pixels=%d", bases, pixels));
// if (pixels <= bases) {
// if (bases % pixels == 0) {
// setBasesPerPixel(bases / pixels);
// }
// else {
// setBasesPerPixel(1 + (bases / pixels));
// }
// }
// else {
// basesPerPixel = (double) 1 / (pixels / bases);
// }
// }
//
// /**
// * Calculate the width in pixels of a feature of the diagram.
// *
// * @param locatedFeature the feature in question
// * @return the width in pixels
// */
// protected int pixelWidth(LocatedFeature locatedFeature) {
// return pixelWidth(locatedFeature.getFmin(), locatedFeature.getFmax());
// }
//
// /**
// * Calculate the width in pixels of a region.
// *
// * @param region the region in question
// * @return the width in pixels
// */
// protected int pixelWidth(Region region) {
// return pixelWidth(region.getFmin(), region.getFmax());
// }
//
// /**
// * Calculate the width in pixels of a segment of the diagram.
// *
// * @param start the start location, in interbase coordinates
// * @param end the end location, in interbase coordinates
// * @return the width in pixels
// */
// protected int pixelWidth(int start, int end) {
// return xCoordinate(end) - xCoordinate(start) + 1;
// }
//
// private double naivePixelWidth(int start, int end) {
// double startPx = start / basesPerPixel;
// double endPx = end / basesPerPixel;
//
// return (endPx - startPx);
// }
//
// protected Gap gapContainingLocation(int loc) {
// Map.Entry<Integer, Gap> e = gapsByStart.lowerEntry(loc);
// if (e == null || loc >= e.getValue().getFmax())
// return null;
// return e.getValue();
// }
//
// /**
// * Calculate the x-position (in pixels relative to this diagram)
// * corresponding to a particular chromosome location.
// *
// * @param loc the chromosome location
// * @return the corresponding x position
// */
// protected int xCoordinate(int loc) {
// return leftMargin + (int) Math.round(xCoordinateAsDoubleIgnoringMargin(loc));
// }
// private double xCoordinateAsDoubleIgnoringMargin(int loc) {
// Gap gapContainingLocation = gapContainingLocation(loc);
// if (gapContainingLocation != null) {
// logger.warn(String.format("Diagram location %d lies in the gap '%s'", loc, gapContainingLocation.getUniqueName()));
// Double gapEndPx = contigs.get(gapContainingLocation.getFmax());
// if (gapEndPx == null)
// throw new IllegalStateException("Failed to locate gap. This should never happen!");
// return gapEndPx - axisBreakGap / 2;
// }
// Map.Entry<Integer, Double> e = contigs.floorEntry(loc);
// if (e == null)
// return naivePixelWidth(getStart(), loc);
//
// return e.getValue() + naivePixelWidth(e.getKey(), loc);
// }
//
// /**
// * Get the colour of the scale.
// * @return the colour of the scale
// */
// public Color getScaleColor() {
// return scaleColor;
// }
//
// /**
// * Set the colour of the scale.
// * @param scaleColor the colour of the scale
// */
// public void setScaleColor(Color scaleColor) {
// this.scaleColor = scaleColor;
// }
//
// /**
// * Get the colour of the labels on the scale track.
// * @return the colour of the labels
// */
// public Color getLabelColor() {
// return labelColor;
// }
//
// /**
// * Set the colour of the labels on the scale track.
// * @param labelColor the colour of the labels
// */
// public void setLabelColor(Color labelColor) {
// this.labelColor = labelColor;
// }
//
// /**
// * Get the distance between minor (unlabelled) ticks on the
// * scale track of this diagram.
// * @return the distance in bases
// */
// public int getMinorTickDistance() {
// return minorTickDistance;
// }
//
// protected int getScaleVerticalPos() {
// return scaleVerticalPos;
// }
//
// protected void setScaleVerticalPos(int scaleVerticalPos) {
// this.scaleVerticalPos = scaleVerticalPos;
// }
//
// /**
// * Set the distance between major (labelled) and minor (unlabelled) ticks on
// * the scale track of this diagram. The <code>majorTickDistance</code>
// * must be a multiple of the <code>minorTickDistance</code>. If the major
// * tick distance is zero, then only minor ticks will be drawn. If the tick
// * distances are both zero, then no scale ticks will be drawn.
// *
// * @param majorTickDistance the distance in bases
// * @param minorTickDistance the distance in bases
// */
// public void setTickDistances(int majorTickDistance, int minorTickDistance) {
// if (minorTickDistance != 0 && majorTickDistance % minorTickDistance != 0)
// throw new IllegalArgumentException(String.format(
// "Major tick distance %d is not a multiple of minor tick distance %d",
// majorTickDistance, minorTickDistance));
//
// this.majorTickDistance = majorTickDistance;
// this.minorTickDistance = minorTickDistance;
//
// if (majorTickDistance == 0)
// scaleVerticalPos = 0;
// }
//
// /**
// * Get the distance between major (labelled) ticks on the scale
// * track of this diagram. Always a multiple of the minor tick distance.
// * @return
// */
// public int getMajorTickDistance() {
// return majorTickDistance;
// }
//
// /**
// * Calculate the y-coordinate of the top of a track.
// *
// * @param trackNumber
// * @return
// */
// protected int topOfTrack(int trackNumber) {
// int firstGuess = (numberOfPositiveTracks - trackNumber) * trackHeight;
//
// // The first guess is right for non-negative tracks.
// // Also, it's always right as long as the scale track is the same height
// // as the ordinary tracks.
// if (scaleTrackHeight == trackHeight || trackNumber >= 0)
// return firstGuess;
//
// // Otherwise, correct for the differing height of the scale track.
// return firstGuess - trackHeight + scaleTrackHeight;
// }
//
// /**
// * Draw the scale line, marking any gaps. Does not draw ticks.
// */
// protected void drawScaleLine() {
// graf.setColor(scaleColor);
// int x = xCoordinate(getStart()), y = yCoordinateOfAxis();
// for (Gap gap: gapsByStart.values()) {
// int gapStartX = xCoordinate(gap.getFmin());
//
// if (labelBackgroundColor != null && axisBreakGap > 0) {
// graf.setColor(labelBackgroundColor);
// graf.fillRect(gapStartX - axisBreakX - 1, y - axisBreakY - 1, 2 * axisBreakX + axisBreakGap + 3, 2 * axisBreakY + 3);
// graf.setColor(scaleColor);
// }
//
// graf.drawLine(x, y, gapStartX, y);
//
// if (axisBreakGap > 0) {
// graf.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// graf.drawLine(gapStartX + axisBreakX, y - axisBreakY, gapStartX - axisBreakX, y + axisBreakY);
// graf.drawLine(gapStartX + axisBreakX + axisBreakGap, y - axisBreakY, gapStartX - axisBreakX + axisBreakGap, y + axisBreakY);
// graf.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
// }
//
// x = xCoordinate(gap.getFmin()) + axisBreakGap;
// }
// graf.drawLine(x, y, xCoordinate(getEnd()), y);
//
// drawBoundaryMarker(xCoordinate(getStart()));
// drawBoundaryMarker(xCoordinate(getEnd()));
// }
//
// protected void drawBoundaryMarker(int x) {
// if (boundaryTickHeight > 0) {
// int y = yCoordinateOfAxis();
// graf.drawLine(x, y - boundaryTickHeight, x, y + boundaryTickHeight);
// }
// }
//
// protected int yCoordinateOfAxis() {
// return topOfTrack(0) + scaleVerticalPos;
// }
//
// protected void drawScaleTrack() {
//
// drawScaleLine();
//
// if (minorTickDistance == 0)
// return;
//
// int majorTicksEvery = (majorTickDistance == 0 ? Integer.MAX_VALUE : (majorTickDistance / minorTickDistance));
// int tickNumber = 0;
// int pos = majorTickDistance * (getStart() / majorTickDistance);
// while (pos <= getEnd()) {
// if (tickNumber++ % majorTicksEvery == 0)
// drawMajorScaleTick(pos);
// else
// drawMinorScaleTick(pos);
// pos += minorTickDistance;
// }
// }
//
// private void drawMinorScaleTick(int pos) {
// drawScaleTick(pos, minorTickHeightAbove, minorTickHeightBelow);
// }
//
// private void drawMajorScaleTick(int pos) {
// if (drawScaleTick(pos, majorTickHeightAbove, majorTickHeightBelow))
// drawLabel(pos);
// }
//
// private void drawLabel(int pos) {
// switch (colorModel) {
// case DIRECT:
// drawLabelDirectly(pos);
// break;
// case INDEXED:
// drawLabelIndirectly(pos);
// }
// }
//
// protected void setLabelRenderingHints(Graphics2D graf) {
// graf.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// graf.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// }
//
// private void drawLabelDirectly(int pos) {
// graf.setFont(labelFont);
// Color previousColor = graf.getColor();
// setLabelRenderingHints(graf);
//
// FontRenderContext fontRenderContext = graf.getFontRenderContext();
// Font font = graf.getFont();
// String labelString = String.valueOf(pos);
// int labelHalfWidth = (int) font.getStringBounds(labelString, fontRenderContext).getCenterX();
// LineMetrics labelMetrics = font.getLineMetrics(labelString, fontRenderContext);
//
// int x = xCoordinate(pos) - labelHalfWidth;
// int y = yCoordinateOfAxis() + majorTickHeightBelow + labelSep + (int) labelMetrics.getAscent();
// if (labelBackgroundColor != null) {
// graf.setColor(labelBackgroundColor);
// graf.fillRect(x, yCoordinateOfAxis() + majorTickHeightBelow + labelSep, labelHalfWidth * 2, (int) labelMetrics.getHeight());
// }
//
// graf.setColor(labelColor);
// graf.drawString(labelString, x, y);
//
// graf.setColor(previousColor);
// }
//
// private void drawLabelIndirectly(int pos) {
// FontRenderContext fontRenderContext = labelGraf.getFontRenderContext();
// Font font = labelGraf.getFont();
// String labelString = String.valueOf(pos);
//
// /*
// * Note: getStringBounds() is not generally guaranteed to give a rectangle
// * that visually encloses all the rendered text, but in this case (printing
// * numerals) it is reasonable to suppose that it will.
// */
// Rectangle2D labelBounds = font.getStringBounds(labelString, fontRenderContext);
// LineMetrics labelMetrics = font.getLineMetrics(labelString, fontRenderContext);
//
// int w = (int) labelBounds.getWidth();
// int h = (int) labelBounds.getHeight();
// if (labelBackgroundColor == null) {
// labelGraf.setColor(TRANSPARENT);
// } else {
// labelGraf.setColor(labelBackgroundColor);
// }
// labelGraf.fillRect(0, 0, w, h);
//
// labelGraf.setColor(labelColor);
// labelGraf.drawString(labelString, 0, labelMetrics.getAscent());
//
// int[] labelData = labelBuffer.getRGB(0, 0, w, h, null, 0, w);
// int x = xCoordinate(pos) - (int) labelBounds.getCenterX();
// int y = yCoordinateOfAxis() + majorTickHeightBelow + labelSep;
//
// /*
// * Deal with the case where the label only partially
// * intersects the image, at the left or right edge
// * of a tile.
// */
// int offset = 0;
// int destWidth = w;
// if (x < 0) {
// offset = -x;
// destWidth += x;
// x = 0;
// } else {
// if (x+w > getWidth()) {
// destWidth = getWidth() - x;
// x = getWidth() - destWidth;
// }
// }
// if (y + h > image.getHeight()) {
// h = image.getHeight() - y;
// }
// // Formula for looking up a (x,y) coordinate
// // pixel = rgbArray[offset + (y-startY)*scansize + (x-startX)];
// try {
// image.setRGB(x, y, destWidth, h, labelData, offset, w);
// }
// catch (NullPointerException exp) {
// // Passing in invalid coordinates - which ones
// logger.error(String.format(
// "NullPointerException x='%d' y='%d' destWidth='%d' h='%d' labelData.size='%d' offset='%d' width='%d'",
// x, y, destWidth, h, labelData.length, offset, w));
// exp.printStackTrace();
// }
// catch (RuntimeException exp) {
// // Passing in invalid coordinates - which ones
// logger.error(String.format(
// "%s x='%d' y='%d' destWidth='%d' h='%d' labelData.size='%d' offset='%d' width='%d'",
// exp.getMessage(), x, y, destWidth, h, labelData.length, offset, w));
// exp.printStackTrace();
// }
// }
//
// /**
// * Draw a scale tick.
// * @param pos the position of the tick, in interbase coordinates.
// * @param tickHeightAbove the height of the tick above the axis, in pixels
// * @param tickHeightBelow the height of the tick below the axis, in pixels
// * @return a boolean value indicating whether the tick has been drawn.
// * If the position falls into a gap, the tick will not be drawn
// * and this method will return <code>false</code>.
// */
// private boolean drawScaleTick(int pos, int tickHeightAbove, int tickHeightBelow) {
//
// if (gapContainingLocation(pos) != null)
// return false;
//
// int topOfTick = yCoordinateOfAxis() - tickHeightAbove;
// int bottomOfTick = yCoordinateOfAxis() + tickHeightBelow;
//
// int x = xCoordinate(pos);
// graf.drawLine(x, topOfTick, x, bottomOfTick);
// return true;
// }
//
// protected int getAxisBreakGap() {
// return axisBreakGap;
// }
//
// protected Color getLabelBackgroundColor() {
// return labelBackgroundColor;
// }
// protected void setLabelBackgroundColor(Color labelBackgroundColor) {
// this.labelBackgroundColor = labelBackgroundColor;
// }
//
// protected void setMargins(int leftMargin, int rightMargin) {
// this.leftMargin = leftMargin;
// this.rightMargin = rightMargin;
// }
//}