com.hfg.graphics.Track2D Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.graphics;
import java.awt.*;
import java.awt.Frame;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.util.*;
import java.util.List;
import java.io.*;
import com.hfg.bio.Strand;
import com.hfg.html.*;
import com.hfg.html.attribute.Shape;
import com.hfg.javascript.PopupMenuJS;
import com.hfg.javascript.TooltipJS;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.image.ImageIO_Util;
import com.hfg.util.mime.MimeType;
import com.hfg.svg.*;
import com.hfg.xml.XMLNode;
import javax.imageio.ImageIO;
//------------------------------------------------------------------------------
/**
* An evidence Track of data to display on genomic coordinates.
*
* @author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// TODO:
// - Vertically center in the track.
public class Track2D extends Rectangle implements Comparable
{
//##########################################################################
// PRIVATE FIELDS
//##########################################################################
private String mName;
private String mDescription;
private String mURL;
private List mForwardGenes;
private List mReverseGenes;
private List mHighlightRegions;
private Boolean mShowId;
private boolean mShowSpeciesInTooltip = true;
private boolean mShowLocationInTooltip = true;
private ColorScale mPctIdColorScale;
private Color mBackgroundColor = Color.white;
// private Color mForwardStrandColor = Color.blue;
// private Color mReverseStrandColor = new Color(150, 150, 255);
private Color mAxisColor = Color.gray;
private Color mLabelColor = Color.black;
private Color mHighlightColor = new Color(150, 250, 150);
private Font mLabelFont = sDefaultLabelFont;
private int mXAxisStartValue;
private int mXAxisEndValue;
private double mXScalingFactor;
private Map mForwardLineMap;
private Map mReverseLineMap;
private int mNumForwardLines;
private int mNumReverseLines;
private List mForwardLineHeights;
private List mReverseLineHeights;
private int mImageWidthInPixels = 700;
private int mForwardImageHeightInPixels;
private int mReverseImageHeightInPixels;
private int mLineHeightInPixels = 10;
private int mLinePaddingInPixels = 3;
private int mDescriptionWidthInPixels = 120;
private int mMinimumBufferInPixels = 3;
private int mRightBufferInPixels = 60;
private int mXAxisImageHeightInPixels;
// private Font mFont = Font.decode("Arial-PLAIN-10");
// Calculated
private int mMajorTickStep;
private int mMinorTickStep;
private ImageMap mForwardImageMap = new ImageMap();
private ImageMap mReverseImageMap = new ImageMap();
private Script mJavascript = new Script(MimeType.TEXT_JAVASCRIPT);
private static FontRenderContext sFRC;
// private static Font sDefaultLabelFont = Font.decode("Courier-PLAIN-9");
private static Font sDefaultLabelFont = new Font("Monospaced", Font.PLAIN, 8);
static
{
Frame frame = new Frame();
frame.addNotify();
Image image =frame.createImage(1, 1);
sFRC = ((Graphics2D) image.getGraphics()).getFontRenderContext();
}
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//--------------------------------------------------------------------------
public Track2D()
{
}
//--------------------------------------------------------------------------
public Track2D(String inName)
{
this();
mName = inName;
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
@Override
public boolean equals(Object o2)
{
return (o2 instanceof Track2D && compareTo((Track2D)o2) == 0);
}
//--------------------------------------------------------------------------
public int compareTo(Track2D inTrack2)
{
int returnValue = 0;
if (this != inTrack2)
{
returnValue = this.getName().compareTo(inTrack2.getName());
}
return returnValue;
}
//--------------------------------------------------------------------------
public boolean hasGenesOnStrand(Strand inStrand)
{
return areAnyGenesInDisplayRegion(Strand.FORWARD == inStrand ? mForwardGenes : mReverseGenes);
}
//--------------------------------------------------------------------------
public void addHighlightRegion(int start, int end)
{
if (null == mHighlightRegions)
{
mHighlightRegions = new ArrayList();
}
mHighlightRegions.add(new HighlightRegion(start, end));
}
//--------------------------------------------------------------------------
public void setGenes(Collection inValues)
{
if (mForwardGenes != null) mForwardGenes.clear();
if (mReverseGenes != null) mReverseGenes.clear();
if (CollectionUtil.hasValues(inValues))
{
for (Gene2D gene : inValues)
{
addGene(gene);
}
}
}
//--------------------------------------------------------------------------
public void addGene(Gene2D inValue)
{
if (inValue.getStrand() == Strand.FORWARD
|| (null == inValue.getStrand()
&& inValue.getEndLocation() > inValue.getStartLocation()))
{
if (null == mForwardGenes)
{
mForwardGenes = new ArrayList();
}
mForwardGenes.add(inValue);
}
else
{
if (null == mReverseGenes)
{
mReverseGenes = new ArrayList();
}
mReverseGenes.add(inValue);
}
}
//--------------------------------------------------------------------------
public Collection getGenes()
{
Collection genes = new ArrayList();
if (CollectionUtil.hasValues(mForwardGenes)) genes.addAll(mForwardGenes);
if (CollectionUtil.hasValues(mReverseGenes)) genes.addAll(mReverseGenes);
return genes;
}
//--------------------------------------------------------------------------
public void setName(String inValue)
{
mName = inValue;
}
//--------------------------------------------------------------------------
public String getName()
{
return mName;
}
//--------------------------------------------------------------------------
public void setURL(String inValue)
{
mURL = inValue;
}
//--------------------------------------------------------------------------
public void setDescription(String inValue)
{
mDescription = inValue;
}
//--------------------------------------------------------------------------
public String getDescription()
{
return mDescription;
}
//--------------------------------------------------------------------------
public void setBackgroundColor(Color inValue)
{
mBackgroundColor = inValue;
}
//--------------------------------------------------------------------------
public Color getBackgroundColor()
{
return mBackgroundColor;
}
//--------------------------------------------------------------------------
public void setPctIdColorScale(ColorScale inValue)
{
mPctIdColorScale = inValue;
}
/*
//--------------------------------------------------------------------------
public void setForwardStrandColor(Color inValue)
{
mForwardStrandColor = inValue;
}
//--------------------------------------------------------------------------
public Color getForwardStrandColor()
{
return mForwardStrandColor;
}
//--------------------------------------------------------------------------
public void setReverseStrandColor(Color inValue)
{
mReverseStrandColor = inValue;
}
//--------------------------------------------------------------------------
public Color getReverseStrandColor()
{
return mReverseStrandColor;
}
*/
//--------------------------------------------------------------------------
public void setLabelColor(Color inValue)
{
mLabelColor = inValue;
}
//--------------------------------------------------------------------------
public Color getLabelColor()
{
return mLabelColor;
}
//--------------------------------------------------------------------------
public void setAxisColor(Color inValue)
{
mAxisColor = inValue;
}
//--------------------------------------------------------------------------
public Color getAxisColor()
{
return mLabelColor;
}
//--------------------------------------------------------------------------
public void setHighlightColor(Color inValue)
{
mHighlightColor = inValue;
}
//--------------------------------------------------------------------------
public Color getHighlightColor()
{
return mHighlightColor;
}
//--------------------------------------------------------------------------
public void setLabelFont(Font inValue)
{
mLabelFont = inValue;
}
//--------------------------------------------------------------------------
public void setXAxisStartValue(int inValue)
{
mXAxisStartValue = inValue;
}
//--------------------------------------------------------------------------
public void setXAxisEndValue(int inValue)
{
mXAxisEndValue = inValue;
}
//--------------------------------------------------------------------------
public int getForwardStrandImageHeight()
{
return mForwardImageHeightInPixels;
}
//--------------------------------------------------------------------------
public int getReverseStrandImageHeight()
{
return mReverseImageHeightInPixels;
}
//--------------------------------------------------------------------------
public void setImageWidth(int inValue)
{
mImageWidthInPixels = inValue;
}
//--------------------------------------------------------------------------
public int getImageWidth()
{
return mImageWidthInPixels;
}
//--------------------------------------------------------------------------
public Script getJavascript()
{
return mJavascript;
}
//--------------------------------------------------------------------------
public ImageMap getImageMap(Strand inStrand)
{
return (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
}
//--------------------------------------------------------------------------
public void setShowId(boolean inValue)
{
mShowId = inValue;
}
//--------------------------------------------------------------------------
public XMLNode toSVG(Strand inStrand)
{
initialize(inStrand);
SVG svg = new SVG();
svg.setFont(mLabelFont);
svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing
SvgGroup group = svg.addGroup();
group.setAttribute("transform", "scale(1)");
int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
mReverseImageHeightInPixels);
// Paint the background
SvgRect background = group.addRect(new Rectangle(new Point(0, 0),
new Dimension(mImageWidthInPixels, imageHeightInPixels)));
background.setFill(getBackgroundColor());
// background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging
// Draw the line separating the description from the alignment
SvgRect separator = group.addRect(new Rectangle(new Point(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0),
new Dimension(mMinimumBufferInPixels, imageHeightInPixels - 2)));
separator.setFill(mAxisColor);
// Write the Track name
Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont);
int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() - (2 * mLinePaddingInPixels) - mMinimumBufferInPixels);
int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2);
group.addText(mName, mLabelFont, new Point(xOffset, yOffset));
// Add highlights
SvgGroup highlightGroup = generateHighlightRegions(imageHeightInPixels);
if (highlightGroup != null) group.addSubtag(highlightGroup);
// Add genes
group.addSubtag(generateGenes(inStrand));
return svg;
}
/*
//--------------------------------------------------------------------------
public SVG writeImageAsSvg(Strand inStrand)
{
if (mLabelFont == sDefaultLabelFont)
{
setLabelFont(Font.decode("Courier-PLAIN-8"));
}
initialize(inStrand);
int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
mReverseImageHeightInPixels);
SvgGraphics2D g2 = new SvgGraphics2D();
draw(g2, inStrand);
SVG svgTag = g2.getRootTag().setWidth(mImageWidthInPixels).setHeight(imageHeightInPixels);
svgTag.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing
return svgTag;
}
*/
//--------------------------------------------------------------------------
public SVG getXAxisAsSVG()
{
mXAxisImageHeightInPixels = (int) TextUtil.getStringRect(mXAxisEndValue + "", mLabelFont).getWidth() + 7;
SVG svg = new SVG();
svg.setFont(mLabelFont);
svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing
SvgGroup group = svg.addGroup();
group.setAttribute("transform", "scale(1)");
// Paint the background
SvgRect background = group.addRect(new Rectangle(new Point(0, 0),
new Dimension(mImageWidthInPixels, mXAxisImageHeightInPixels)));
background.setFill(getBackgroundColor());
// background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging
// Draw the x-axis line
SvgLine axisLine = group.addLine(new Point(mDescriptionWidthInPixels, 1),
new Point(mImageWidthInPixels - mRightBufferInPixels, 1));
// axisLine.setFill(mAxisColor);
// Add highlights
SvgGroup highlightGroup = generateHighlightRegions(mXAxisImageHeightInPixels);
if (highlightGroup != null) group.addSubtag(highlightGroup);
/*
// Write the Track name
Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont);
int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() - (2 * mLinePaddingInPixels) - mMinimumBufferInPixels);
int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2);
group.addText(mName, mLabelFont, new Point(xOffset, yOffset));
*/
determineTickSize();
double lastXTranslation = 0;
int yOffset = 2;
for (Integer tickValue : getTickValues())
{
if (tickValue%mMajorTickStep == 0
|| tickValue == mXAxisStartValue
|| tickValue == mXAxisEndValue)
{
yOffset = 6;
Rectangle textBoundBox = TextUtil.getStringRect(tickValue + "", mLabelFont);
double xTranslation = mDescriptionWidthInPixels
+ ((tickValue - mXAxisStartValue) * getXScalingFactor())
- textBoundBox.getHeight() / 4;
if (xTranslation - lastXTranslation > 8)
{
SvgText label = group.addText(tickValue + "", mLabelFont, new Point((int)xTranslation, yOffset + 5));
label.setTransform("rotate(45 " + (int) (xTranslation - 3) + " " + (yOffset + 5) + ")");
lastXTranslation = xTranslation;
}
/*
if (xTranslation - lastXTranslation > 8
&& (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10))
{
double yTranslation = yOffset + layout.getAdvance() + 1;
g2.translate(xTranslation, yTranslation);
g2.rotate(-Math.PI / 2);
g2.drawString(tickValue + "", 0, 0);
g2.setTransform(origTransform);
lastXTranslation = xTranslation;
}
*/
}
else if (tickValue%mMinorTickStep == 0)
{
yOffset = 2;
}
else
{
continue;
}
int xOffset = mDescriptionWidthInPixels
+ (int) ((tickValue - mXAxisStartValue) * getXScalingFactor());
SvgLine tickLine = group.addLine(new Point(xOffset, 1),
new Point(xOffset, yOffset));
// tickLine.setFill(mAxisColor);
}
return svg;
}
//--------------------------------------------------------------------------
public void draw(Graphics2D g2, Strand inStrand)
{
if (inStrand == Strand.FORWARD) mForwardImageMap = new ImageMap();
else mReverseImageMap = new ImageMap();
// TODO: Split mJavascript into forward and reverse?
// Save settings
Paint origPaint = g2.getPaint();
Font origFont = g2.getFont();
// Save inital location.
AffineTransform origTransform = g2.getTransform();
// Enable anti-aliasing
// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
// RenderingHints.VALUE_ANTIALIAS_ON);
int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
mReverseImageHeightInPixels);
// Paint the background
setSize(mImageWidthInPixels, imageHeightInPixels);
g2.setPaint(getBackgroundColor());
g2.fill(this);
// Draw the line separating the description from the alignment
g2.setPaint(mAxisColor);
// g2.drawLine(mDescriptionWidthInPixels - mMinimumBufferInPixels, 0,
// mDescriptionWidthInPixels - mMinimumBufferInPixels, imageHeightInPixels - 2);
g2.fillRect(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0,
mMinimumBufferInPixels, imageHeightInPixels - 2);
// Write the Track name
g2.setPaint(mLabelColor);
g2.setFont(mLabelFont);
FontRenderContext frc = g2.getFontRenderContext();
TextLayout layout = new TextLayout(mName, mLabelFont, frc);
Rectangle textBounds = (g2 instanceof SvgGraphics2D ? TextUtil.getStringRect(mName, mLabelFont) : layout.getBounds().getBounds());
int xOffset = (int) (mDescriptionWidthInPixels - textBounds.getWidth()) - 2 * mLinePaddingInPixels - mMinimumBufferInPixels;
int yOffset = (int) (imageHeightInPixels/2 + textBounds.getHeight()/2);
g2.drawString(mName, xOffset, yOffset);
if (mURL != null)
{
// Set image map data
ImageMap imageMap = (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
Area area = imageMap.addArea();
area.setHref(mURL);
area.setShape(Shape.RECT);
area.setCoords(xOffset + "," + (yOffset - textBounds.getHeight()) + ","
+ (xOffset + textBounds.getWidth()) + "," + yOffset);
}
drawHighlightRegions(g2, imageHeightInPixels);
drawGenes(g2, inStrand);
// Return to orig. location
g2.setTransform(origTransform);
// Restore settings
g2.setPaint(origPaint);
g2.setFont(origFont);
}
//--------------------------------------------------------------------------
public Image getImage(Strand inStrand)
{
initialize(inStrand);
int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
mReverseImageHeightInPixels);
Frame frame = new Frame();
frame.addNotify();
Image image = frame.createImage(mImageWidthInPixels, imageHeightInPixels);
Graphics2D g2 = (Graphics2D) image.getGraphics();
draw(g2, inStrand);
return image;
}
//--------------------------------------------------------------------------
public BufferedImage getBufferedImage(Strand inStrand)
{
initialize(inStrand);
int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
mReverseImageHeightInPixels);
Frame frame = new Frame();
frame.addNotify();
BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
imageHeightInPixels,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
draw(g2, inStrand);
return bufferedImage;
}
//--------------------------------------------------------------------------
public BufferedImage getXAxisBufferedImage()
{
Frame frame = new Frame();
frame.addNotify();
BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
mXAxisImageHeightInPixels,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
drawXAxis(g2);
return bufferedImage;
}
//--------------------------------------------------------------------------
public void writeImageAsJpeg(OutputStream inStream, Strand inStrand)
throws IOException
{
BufferedImage bufferedImage = getBufferedImage(inStrand);
ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
}
//--------------------------------------------------------------------------
public void writeXAxisImageAsJpeg(OutputStream inStream)
throws IOException
{
mXAxisImageHeightInPixels = getLabelWidthInPixels(mXAxisEndValue + "") + 7;
BufferedImage bufferedImage = getXAxisBufferedImage();
ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
}
//--------------------------------------------------------------------------
public void writeImageAsPng(OutputStream inStream, Strand inStrand)
throws IOException
{
BufferedImage bufferedImage = getBufferedImage(inStrand);
ImageIO.write(bufferedImage, "png", inStream);
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private void initialize(Strand inStrand)
{
List genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);
if (genes != null)
{
for (Gene2D gene : genes)
{
initializeGene(gene);
}
}
// Figure out how many lines high the image will be and set the image height.
createLineMap(inStrand);
}
//--------------------------------------------------------------------------
private void initializeGene(Gene2D inGene)
{
inGene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
inGene.setXScalingFactor(getXScalingFactor());
// inGene.setForwardStrandColor(getForwardStrandColor());
// inGene.setReverseStrandColor(getReverseStrandColor());
if (mLabelFont != null)
{
inGene.setLabelFont(mLabelFont);
}
if (mShowId != null)
{
inGene.setShowId(mShowId);
}
}
//--------------------------------------------------------------------------
private boolean areAnyGenesInDisplayRegion(List inGenes)
{
boolean result = false;
if (inGenes != null)
{
for (Gene2D gene : inGenes)
{
gene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
if (gene.inDisplayRegion())
{
result = true;
break;
}
}
}
return result;
}
//--------------------------------------------------------------------------
private double getXScalingFactor()
{
if (0 == mXScalingFactor)
{
mXScalingFactor = (double)(mImageWidthInPixels - mDescriptionWidthInPixels - mRightBufferInPixels)
/(mXAxisEndValue - mXAxisStartValue + 1);
}
return mXScalingFactor;
}
//--------------------------------------------------------------------------
private int getScaledPosition(int inPosition)
{
int xOffset = (int) ((inPosition - mXAxisStartValue) * getXScalingFactor());
return xOffset;
}
//--------------------------------------------------------------------------
private Gene2D getLastGeneForLevel(int inLevel, List> inLevelLists)
{
Gene2D outGene = null;
if (inLevelLists.size() >= inLevel)
{
List levelList = inLevelLists.get(inLevel - 1);
outGene = levelList.get(levelList.size() - 1);
}
return outGene;
}
//--------------------------------------------------------------------------
private void addGeneToLevel(Gene2D inGene, int inLevel, List> inLevelLists)
{
List levelList = null;
if (inLevelLists.size() < inLevel)
{
levelList = new ArrayList();
inLevelLists.add(levelList);
}
else
{
levelList = inLevelLists.get(inLevel - 1);
}
levelList.add(inGene);
}
//--------------------------------------------------------------------------
private boolean genesAreTooCloseTogether(Gene2D inGene1, Gene2D inGene2)
{
boolean resultValue = false;
if (getScaledPosition(inGene1.getRight()) + (mShowId && inGene1.getShowId() ? inGene1.getLabelWidthInPixels() : 0)
+ mMinimumBufferInPixels > getScaledPosition(inGene2.getLeft()))
{
resultValue = true;
}
return resultValue;
}
//--------------------------------------------------------------------------
private void createLineMap(Strand inStrand)
{
Map lineMap = null;
List> levelLists = new ArrayList>();
List genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);
if (genes != null)
{
Collections.sort(genes);
lineMap = new HashMap();
for (Gene2D gene : genes)
{
if (gene.inDisplayRegion())
{
int level = 1;
boolean assigningLevel = true;
while (assigningLevel)
{
Gene2D lastGene = getLastGeneForLevel(level, levelLists);
if (lastGene != null
&& genesAreTooCloseTogether(lastGene, gene))
{
level++;
}
else
{
addGeneToLevel(gene, level, levelLists);
lineMap.put(gene, level + "");
assigningLevel = false;
}
}
}
}
}
if (inStrand == Strand.FORWARD)
{
mForwardLineMap = lineMap;
mNumForwardLines = levelLists.size();
mForwardLineHeights = determineLineHeights(levelLists);
// mForwardImageHeightInPixels = mNumForwardLines * (mLineHeightInPixels + mLinePaddingInPixels);
mForwardImageHeightInPixels = sum(mForwardLineHeights) + mForwardLineHeights.size() * mLinePaddingInPixels;
}
else
{
mReverseLineMap = lineMap;
mNumReverseLines = levelLists.size();
mReverseLineHeights = determineLineHeights(levelLists);
// mReverseImageHeightInPixels = mNumReverseLines * (mLineHeightInPixels + mLinePaddingInPixels);
mReverseImageHeightInPixels = sum(mReverseLineHeights) + mReverseLineHeights.size() * mLinePaddingInPixels;
}
}
//--------------------------------------------------------------------------
private int sum(List inValues)
{
int sum = 0;
if (CollectionUtil.hasValues(inValues))
{
for (Integer value : inValues)
{
sum += value;
}
}
return sum;
}
//--------------------------------------------------------------------------
private List determineLineHeights(List> inLevelLists)
{
List lineHeights = new ArrayList(inLevelLists.size());
for (List level : inLevelLists)
{
double maxLineHeight = 0;
for (Gene2D gene : level)
{
if (gene.getHeightInPixels() > maxLineHeight) maxLineHeight = gene.getHeightInPixels();
}
lineHeights.add((int)maxLineHeight);
}
return lineHeights;
}
//--------------------------------------------------------------------------
private int getYOffsetForLine(int inLine, Strand inStrand)
{
int yOffset = 0;
List lineHeights = (inStrand == Strand.FORWARD ? mForwardLineHeights : mReverseLineHeights);
for (int i = 0; i < inLine; i++)
{
yOffset -= lineHeights.get(i);
}
yOffset -= inLine * mLinePaddingInPixels;
return yOffset;
}
//--------------------------------------------------------------------------
private void drawHighlightRegions(Graphics2D g2, int inImageHeight)
{
if (mHighlightRegions != null)
{
for (HighlightRegion region : mHighlightRegions)
{
int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor());
int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor());
if (width < 1) width = 1;
g2.setPaint(mHighlightColor);
g2.fillRect(xOffset, 0, width, inImageHeight);
}
}
}
//--------------------------------------------------------------------------
private SvgGroup generateHighlightRegions(int inImageHeight)
{
SvgGroup highlightGroup = null;
if (mHighlightRegions != null)
{
highlightGroup = new SvgGroup();
for (HighlightRegion region : mHighlightRegions)
{
int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor());
int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor());
if (width < 1) width = 1;
highlightGroup.addRect(new Rectangle(new Point(xOffset, 0),
new Dimension(width, inImageHeight))).setFill(mHighlightColor);
}
}
return highlightGroup;
}
//--------------------------------------------------------------------------
private SvgGroup generateGenes(Strand inStrand)
{
// Move to the bottom left of the genomic display
int imageHeightInPixels = 0;
List genes = null;
Map lineMap = null;
if (inStrand == Strand.FORWARD)
{
imageHeightInPixels = mForwardImageHeightInPixels;
genes = mForwardGenes;
lineMap = mForwardLineMap;
}
else
{
imageHeightInPixels = mReverseImageHeightInPixels;
genes = mReverseGenes;
lineMap = mReverseLineMap;
}
SvgGroup group = new SvgGroup().setTransform("translate(" + mDescriptionWidthInPixels + ", " + imageHeightInPixels + ")");
if (genes != null)
{
for (Gene2D gene : genes)
{
if (gene.inDisplayRegion())
{
SvgLink svgLink = null;
List urls = gene.getURLs();
if (CollectionUtil.hasValues(urls))
{
if (1 == urls.size())
{
svgLink = new SvgLink(urls.get(0).getURL());
}
else
{
PopupMenuJS menu = new PopupMenuJS();
menu.setMenuTitle(gene.getId());
for (Link link : urls)
{
menu.addLink(link);
}
// Add the menu to the track's javascript
mJavascript.addContent(menu.generateMenuJavascript());
svgLink = new SvgLink(menu.getDisplayMenuJavascript());
}
}
SvgNode svgNode = null;
if (svgLink != null)
{
svgNode = svgLink;
}
else
{
svgNode = new SvgGroup();
}
group.addSubtag(svgNode);
// Move to gene location
// What line is the gene on? Move to upper left of gene.
int line = Integer.parseInt(lineMap.get(gene));
if (inStrand == Strand.REVERSE)
{
line = mNumReverseLines - line + 1;
}
//XXXX int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor());
int geneLeft = (int) (gene.getLeft() * mXScalingFactor);
int bound = (int) (mXAxisStartValue * mXScalingFactor);
int xOffset = geneLeft - bound;
// int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor());
// Don't back up into the description region.
if (xOffset < 0) xOffset = 0;
/* // Don't go off the right side of the display region.
if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels)
{
xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels;
}
*/
// int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels);
int yOffset = getYOffsetForLine(line, inStrand);
// SvgGroup geneGroup = group.addGroup().setTransform();
svgNode.setTransform("translate(" + xOffset + ", " + yOffset + ")");
svgNode.addSubtag(gene.toSVG());
}
}
}
return group;
}
//--------------------------------------------------------------------------
private void drawGenes(Graphics2D g2, Strand inStrand)
{
TooltipJS tooltip = new TooltipJS();
int imageHeightInPixels = 0;
List genes = null;
Map lineMap = null;
ImageMap imageMap = null;
if (inStrand == Strand.FORWARD)
{
imageHeightInPixels = mForwardImageHeightInPixels;
genes = mForwardGenes;
lineMap = mForwardLineMap;
imageMap = mForwardImageMap;
}
else
{
imageHeightInPixels = mReverseImageHeightInPixels;
genes = mReverseGenes;
lineMap = mReverseLineMap;
imageMap = mReverseImageMap;
}
// Move to the bottom left of the genomic display
g2.translate(mDescriptionWidthInPixels, imageHeightInPixels);
if (genes != null)
{
// Save initial location.
AffineTransform origTransform = g2.getTransform();
for (Gene2D gene : genes)
{
if (gene.inDisplayRegion())
{
// Dynamically set the gene model color based on the mapped %id?
if (mPctIdColorScale != null
&& null == gene.getColor()
&& gene.getSeqMappingCoverage() != null
&& gene.getSeqMappingCoverage().getPercentIdentity() != null)
{
gene.setColor(mPctIdColorScale.getColor(gene.getSeqMappingCoverage().getPercentIdentity() / 100));
}
// Move to gene location
// What line is the gene on? Move to upper left of gene.
int line = Integer.parseInt(lineMap.get(gene));
if (inStrand == Strand.REVERSE)
{
line = mNumReverseLines - line + 1;
}
//XXXX int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor());
int geneLeft = (int) (gene.getLeft() * mXScalingFactor);
int bound = (int) (mXAxisStartValue * mXScalingFactor);
int xOffset = geneLeft - bound;
// int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor());
// Don't back up into the description region.
if (xOffset < 0) xOffset = 0;
/* // Don't go off the right side of the display region.
if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels)
{
xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels;
}
*/
// int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels);
int yOffset = getYOffsetForLine(line, inStrand);
g2.translate(xOffset, yOffset);
gene.draw(g2);
// Return to orig. location
g2.setTransform(origTransform);
// Set image map data
Area area = imageMap.addArea();
setAreaURL(area, gene);
tooltip.addTooltip(area, gene.getTooltipContent());
area.setShape(Shape.RECT);
int x1 = mDescriptionWidthInPixels + xOffset;
if (x1 < mDescriptionWidthInPixels) x1 = mDescriptionWidthInPixels;
int x2 = mDescriptionWidthInPixels + xOffset + gene.getScaledWidthInPixels()
+ (mShowId ? gene.getLabelWidthInPixels() : 0);
if (x2 > mImageWidthInPixels)
{
x2 = mImageWidthInPixels;
}
area.setCoords(x1 + "," + (imageHeightInPixels + yOffset) + ","
+ x2 + "," + (imageHeightInPixels + yOffset + gene.getHeightInPixels() - 1));
}
}
}
}
//--------------------------------------------------------------------------
private void setAreaURL(Area inArea, Gene2D inGene)
{
List urls = inGene.getURLs();
if (CollectionUtil.hasValues(urls))
{
if (1 == urls.size())
{
Link link = urls.get(0);
inArea.setHref(link.getURL());
}
else
{
PopupMenuJS menu = new PopupMenuJS();
menu.setMenuTitle(inGene.getId());
for (Link link : urls)
{
menu.addLink(link);
}
// Add the menu to the track's javascript
mJavascript.addContent(menu.generateMenuJavascript());
inArea.setHref(menu.getDisplayMenuJavascript());
}
}
}
//--------------------------------------------------------------------------
public void drawXAxis(Graphics2D g2)
{
// Save settings
Paint origPaint = g2.getPaint();
Font origFont = g2.getFont();
// Save inital location.
AffineTransform origTransform = g2.getTransform();
// Enable anti-aliasing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Paint the background
g2.setPaint(Color.white);
g2.fillRect(0, 0, mImageWidthInPixels, mXAxisImageHeightInPixels);
drawHighlightRegions(g2, mXAxisImageHeightInPixels);
// Draw the x-axis line
g2.setPaint(mAxisColor);
g2.drawLine(mDescriptionWidthInPixels, 0,
mImageWidthInPixels - mRightBufferInPixels, 0);
determineTickSize();
double lastXTranslation = 0;
int yOffset = 2;
for (Integer tickValue : getTickValues())
{
if (tickValue%mMajorTickStep == 0
|| tickValue == mXAxisStartValue
|| tickValue == mXAxisEndValue)
{
yOffset = 4;
g2.setFont(mLabelFont);
FontRenderContext frc = g2.getFontRenderContext();
TextLayout layout = new TextLayout(tickValue + "", mLabelFont, frc);
double xTranslation = mDescriptionWidthInPixels
+ ((tickValue - mXAxisStartValue) * getXScalingFactor())
+ layout.getBounds().getHeight() / 2;
if (xTranslation - lastXTranslation > 8
&& (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10))
{
double yTranslation = yOffset + layout.getAdvance() + 1;
g2.translate(xTranslation, yTranslation);
g2.rotate(-Math.PI / 2);
g2.drawString(tickValue + "", 0, 0);
g2.setTransform(origTransform);
lastXTranslation = xTranslation;
}
}
else if (tickValue%mMinorTickStep == 0)
{
yOffset = 2;
}
else
{
continue;
}
int xOffset = mDescriptionWidthInPixels
+ (int) ((tickValue - mXAxisStartValue) * getXScalingFactor());
g2.drawLine(xOffset, 0,
xOffset, yOffset);
}
// Return to orig. location
g2.setTransform(origTransform);
// Restore settings
g2.setPaint(origPaint);
g2.setFont(origFont);
}
//--------------------------------------------------------------------------
private void determineTickSize()
{
int xAxisWidth = mXAxisEndValue - mXAxisStartValue + 1;
String widthString = xAxisWidth + "";
mMajorTickStep = (int) Math.pow(10, widthString.length() - 1);
mMinorTickStep = mMajorTickStep / 10;
}
//--------------------------------------------------------------------------
private List getTickValues()
{
List tickValues = new ArrayList();
// First add the major ticks
tickValues.add(mXAxisStartValue);
for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMajorTickStep) * mMajorTickStep);
tickValue < mXAxisEndValue;
tickValue += mMajorTickStep)
{
if (tickValue > mXAxisStartValue)
{
tickValues.add(tickValue);
}
}
tickValues.add(mXAxisEndValue);
// Now add the minor ticks
for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMinorTickStep) * mMinorTickStep);
tickValue < mXAxisEndValue;
tickValue += mMinorTickStep)
{
if (tickValue%mMajorTickStep != 0
&& tickValue != mXAxisStartValue)
{
tickValues.add(tickValue);
}
}
return tickValues;
}
//--------------------------------------------------------------------------
public int getLabelWidthInPixels(String inLabel)
{
int widthInPixels = 0;
if (inLabel != null
&& inLabel.length() > 0)
{
TextLayout layout = new TextLayout(inLabel, mLabelFont, sFRC);
widthInPixels = (int) layout.getBounds().getWidth() + 1;
}
return widthInPixels;
}
//--------------------------------------------------------------------------
private class HighlightRegion
{
private int mStart;
private int mEnd;
//-----------------------------------------------------------------------
public HighlightRegion(int inStart, int inEnd)
{
if (inEnd < inStart)
{
mStart = inEnd;
mEnd = inStart;
}
else
{
mStart = inStart;
mEnd = inEnd;
}
}
//-----------------------------------------------------------------------
public int getStart()
{
return mStart;
}
//-----------------------------------------------------------------------
public int getEnd()
{
return mEnd;
}
}
}