com.hfg.bio.seq.PlasmidMap 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.bio.seq;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.hfg.bio.Strand;
import com.hfg.bio.molbio.RestrictionEnzyme;
import com.hfg.bio.molbio.RestrictionSite;
import com.hfg.bio.seq.format.feature.BasicFeatureKey;
import com.hfg.bio.seq.format.feature.FeatureKey;
import com.hfg.bio.seq.format.feature.FeatureQualifier;
import com.hfg.bio.seq.format.feature.SeqFeature;
import com.hfg.bio.seq.format.feature.genbank.GenBankFeatureKey;
import com.hfg.bio.seq.format.feature.genbank.GenBankFeatureQualifierName;
import com.hfg.graphics.units.GfxUnits;
import com.hfg.html.HTML;
import com.hfg.html.attribute.HTMLColor;
import com.hfg.math.Quadrant;
import com.hfg.math.Range;
import com.hfg.svg.*;
import com.hfg.svg.attribute.SvgTextAnchor;
import com.hfg.svg.attribute.SvgTextPathMethod;
import com.hfg.svg.attribute.SvgTextPathSpacing;
import com.hfg.svg.attribute.SvgVerticalAlign;
import com.hfg.svg.path.SvgPathClosePathCmd;
import com.hfg.svg.path.SvgPathEllipticalArcCmd;
import com.hfg.svg.path.SvgPathLineToCmd;
import com.hfg.svg.path.SvgPathMoveToCmd;
import com.hfg.util.CompareUtil;
import com.hfg.util.Recursion;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
//------------------------------------------------------------------------------
/**
* Plasmid map generator.
*
* @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]
//------------------------------------------------------------------------------
public class PlasmidMap
{
private NucleicAcid mSeq;
private PlasmidMapSettings mSettings;
private Point2D mCenterPoint = new Point2D.Double(0, 0);
private double mOrigin = 90;
// Cached values
private double mBackboneRadiusPx;
private double mTickLengthPx;
private int mMajorTickStep;
private int mMinorTickStep;
private List mMajorTickPositions;
private List mMinorTickPositions;
private Integer mPathIdSrc = 0;
private Map mFeatureColorMap;
private Color mDefaultFeatureColor = HTMLColor.LIGHT_GRAY;
private static FontRenderContext sFRC = new FontRenderContext(new AffineTransform(), true, true);
// Labeling CSS classes
private static final String STEM = "stem";
private static final String STEMMED_LABEL = "stemmedLabel";
//**************************************************************************
// CONSTRUCTORS
//**************************************************************************
//---------------------------------------------------------------------------
public PlasmidMap(NucleicAcid inSeq, PlasmidMapSettings inSettings)
{
mSeq = inSeq;
mSettings = inSettings;
}
//**************************************************************************
// PUBLIC METHODS
//**************************************************************************
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
public SVG toSVG()
{
initSVG();
SVG svg = new SVG()
.setWidth((int) mSettings.getWidth().to(GfxUnits.pixels))
.setHeight((int) mSettings.getHeight().to(GfxUnits.pixels));
mCenterPoint = new Point2D.Double(mSettings.getWidth().to(GfxUnits.pixels) / 2, mSettings.getHeight().to(GfxUnits.pixels) / 2);
double backboneRadiusPx = mSettings.getBackboneRadius().to(GfxUnits.pixels);
double tickLengthPx = mSettings.getTickLength().to(GfxUnits.pixels);
// Backbone
svg.addCircle()
.setR(backboneRadiusPx)
.setCenter(mCenterPoint)
.setFill(null)
.setStroke(HTMLColor.GREY)
.setStrokeWidth(3);
// Add the plasmid title in the center
svg.addSubtag(generateTitle());
// Add the plasmid size in the center
svg.addText(StringUtil.generateLocalizedNumberString(mSeq.length()) + " bp")
.setTextAnchor(SvgTextAnchor.middle)
.setVerticalAlign(SvgVerticalAlign.first)
.addStyle(mSettings.getSubTitleStyle())
.setX((int)mCenterPoint.getX())
.setY((int)mCenterPoint.getY() + 20);
// Determine the major and minor tick positions
// Add ticks
svg.addSubtag(generateTicks());
// Add features
svg.addSubtag(generateFeatures());
// Add restriction sites
svg.addSubtag(generateRestrictionSites());
// Finally, the labels might be overlapping.
// Try to adjust them if that is the case.
resolveOverlappingLabels(svg);
return svg;
}
//**************************************************************************
// PRIVATE METHODS
//**************************************************************************
//---------------------------------------------------------------------------
private void initSVG()
{
mBackboneRadiusPx = mSettings.getBackboneRadius().to(GfxUnits.pixels);
mTickLengthPx = mSettings.getTickLength().to(GfxUnits.pixels);
mPathIdSrc = 0;
mFeatureColorMap = mSettings.getFeatureColorMap();
}
//---------------------------------------------------------------------------
private Point2D getCenter()
{
return mCenterPoint;
}
//---------------------------------------------------------------------------
private double getOrigin()
{
return mOrigin;
}
//---------------------------------------------------------------------------
private double bpToRadians(int inBp)
{
double radians;
radians = (inBp * ((Math.PI * 2.0) / mSeq.length())) - ((Math.PI / 180.0) * getOrigin());
return radians;
}
//---------------------------------------------------------------------------
private SvgNode generateTitle()
{
SvgGroup g = new SvgGroup();
String title = mSeq.getDescription();
if (StringUtil.isSet(title))
{
if (title.endsWith("."))
{
title = title.substring(0, title.length() - 1);
}
if (title.endsWith(", complete sequence"))
{
title = title.substring(0, title.length() - 19);
}
}
if (! StringUtil.isSet(title)
|| title.length() > 50)
{
title = mSeq.getID();
}
g.addText(title)
.setTextAnchor(SvgTextAnchor.middle)
.setVerticalAlign(SvgVerticalAlign.first)
.addStyle(mSettings.getTitleStyle())
.setX((int)mCenterPoint.getX())
.setY((int)mCenterPoint.getY());
return g;
}
//---------------------------------------------------------------------------
private SvgNode generateTicks()
{
// Determine the major and minor tick positions
determineTickPositions();
SvgGroup g = new SvgGroup().setId("Ticks");
// Minor ticks
for (Integer bp : mMinorTickPositions)
{
g.addSubtag(generateTick(bp, mTickLengthPx / 2));
}
// Major ticks
for (Integer bp : mMajorTickPositions)
{
g.addSubtag(generateTick(bp, mTickLengthPx));
}
// Major tick labels
Font tickLabelFont = mSettings.getTickLabelFont();
int tickLength = (int) mSettings.getTickLength().to(GfxUnits.pixels);
for (Integer bp : mMajorTickPositions)
{
String label = bp + "";
TextLayout layout = new TextLayout(label, tickLabelFont, sFRC);
Rectangle2D bounds = layout.getBounds();
double labelHeight = bounds.getHeight();
double labelWidth = bounds.getWidth();
double radians = bpToRadians(bp);
double x = mCenterPoint.getX() + (Math.cos(radians) * (mBackboneRadiusPx + -1 * tickLength - 5));
double y = mCenterPoint.getY() + (Math.sin(radians) * (mBackboneRadiusPx + -1 * tickLength - 5));
// Adjust the position
if ( (Math.sin(radians) <= 1.0d)
&& (Math.sin(radians) >= 0.0d)
&& (Math.cos(radians) >= 0.0d)
&& (Math.cos(radians) <= 1.0d))
{
// 0 to 90 degrees
x = x - 6 - labelWidth + ((0.5 * labelWidth) * Math.sin(radians));
y = y + 0.5 * labelHeight - ((0.5 * labelHeight) * Math.sin(radians));
}
else if ( (Math.sin(radians) <= 1.0d)
&& (Math.sin(radians) >= 0.0d)
&& (Math.cos(radians) <= 0.0d)
&& (Math.cos(radians) >= -1.0d))
{
// 90 to 180 degrees
x = x - ((0.5 * labelWidth) * Math.sin(radians));
y = y + 0.5 * labelHeight - ((0.5 * labelHeight) * Math.sin(radians));
}
else if ( (Math.sin(radians) <= 0.0d)
&& (Math.sin(radians) >= -1.0d)
&& (Math.cos(radians) <= 0.0d)
&& (Math.cos(radians) >= -1.0d))
{
// 180 to 270 degrees
x = x + 4 + ((0.5 * labelWidth) * Math.sin(radians));
y = y + 4 + 0.5 * labelHeight - ((0.5 * labelHeight) * Math.sin(radians));
}
else if ((Math.sin(radians) == -1.0))
{
// 270 degrees
x = x - labelWidth - ((0.5 * labelWidth) * Math.sin(radians));
y = y + 4 + 0.5 * labelHeight - ((0.5 * labelHeight) * Math.sin(radians));
}
else
{
// 270 to 360 degrees
x = x - 4 - labelWidth - ((0.5 * labelWidth) * Math.sin(radians));
y = y + 4 + 0.5 * labelHeight - ((0.5 * labelHeight) * Math.sin(radians));
}
g.addText(label)
.setFont(tickLabelFont)
.setX((int)x)
.setY((int)y);
}
return g;
}
//---------------------------------------------------------------------------
private void determineTickPositions()
{
String widthString = mSeq.length() + "";
mMajorTickStep = (int) Math.pow(10, widthString.length() - 1);
if (0 == mMajorTickStep%mSeq.length()
|| mMajorTickStep > 0.5 * mSeq.length())
{
mMajorTickStep = mMajorTickStep / 10;
}
mMinorTickStep = mMajorTickStep / 10;
mMajorTickPositions = new ArrayList<>(10);
mMinorTickPositions = new ArrayList<>(100);
for (int bp = 0; bp < mSeq.length(); bp+= mMinorTickStep)
{
if (bpToRadians(bp) > 4.65)
{
break;
}
if (0 == bp
|| 0 == bp%mMajorTickStep)
{
mMajorTickPositions.add(bp);
}
else
{
mMinorTickPositions.add(bp);
}
}
}
//---------------------------------------------------------------------------
private SvgNode generateTick(int inBp, double inTickLength)
{
double radians = bpToRadians(inBp);
double startX = mCenterPoint.getX() + (Math.cos(radians) * mBackboneRadiusPx);
double startY = mCenterPoint.getY() + (Math.sin(radians) * mBackboneRadiusPx);
double endX = mCenterPoint.getX() + (Math.cos(radians) * (mBackboneRadiusPx + -1 * inTickLength));
double endY = mCenterPoint.getY() + (Math.sin(radians) * (mBackboneRadiusPx + -1 * inTickLength));
return new SvgLine(new Point2D.Double(startX, startY), new Point2D.Double(endX, endY)).addStyle(mSettings.getTickStyle());
}
//---------------------------------------------------------------------------
private SvgNode generateFeatures()
{
SvgGroup g = new SvgGroup().setId("Features");
List featureTypes = mSettings.getFeatureTypes();
if (CollectionUtil.hasValues(featureTypes))
{
List allFeatures = new ArrayList<>(25);
for (String featureType : featureTypes)
{
FeatureKey key = new BasicFeatureKey(featureType);
List features = mSeq.getFeatures(key);
if (CollectionUtil.hasValues(features))
{
allFeatures.addAll(features);
}
}
if (CollectionUtil.hasValues(allFeatures))
{
Collections.sort(allFeatures);
for (SeqFeature feature : allFeatures)
{
g.addSubtag(generateFeature(feature));
}
}
}
return g;
}
//---------------------------------------------------------------------------
private SvgNode generateFeature(SeqFeature inFeature)
{
SvgNode node;
if (inFeature.name().name().equalsIgnoreCase(GenBankFeatureKey.CDS.name()))
{
if (Strand.FORWARD.equals(inFeature.getLocation().getStrand()))
{
node = generateFwdFrameFeature(inFeature);
}
else
{
node = generateRevFrameFeature(inFeature);
}
}
else
{
node = generateDirectionlessFeature(inFeature);
}
return node;
}
//---------------------------------------------------------------------------
private SvgNode generateFwdFrameFeature(SeqFeature inFeature)
{
Range location = inFeature.getLocation().toIntRange();
double featureRadiusPx = mBackboneRadiusPx + 3;
int featureHeight = 20;
SvgGroup g = new SvgGroup();
double arrowheadSize = 0.05; // in radians
double startRadians = bpToRadians(location.getStart());
double endRadians = bpToRadians(location.getEnd());
if (endRadians - startRadians < arrowheadSize)
{
arrowheadSize = (endRadians - startRadians);
}
double start1X = mCenterPoint.getX() + (Math.cos(startRadians) * (featureRadiusPx));
double start1Y = mCenterPoint.getY() + (Math.sin(startRadians) * (featureRadiusPx));
double start2X = mCenterPoint.getX() + (Math.cos(startRadians) * (featureRadiusPx + featureHeight));
double start2Y = mCenterPoint.getY() + (Math.sin(startRadians) * (featureRadiusPx + featureHeight));
double headStart1X = mCenterPoint.getX() + (Math.cos(endRadians - arrowheadSize) * (featureRadiusPx + featureHeight));
double headStart1Y = mCenterPoint.getY() + (Math.sin(endRadians - arrowheadSize) * (featureRadiusPx + featureHeight));
double tipX = mCenterPoint.getX() + (Math.cos(endRadians) * (featureRadiusPx + (featureHeight/2)));
double tipY = mCenterPoint.getY() + (Math.sin(endRadians) * (featureRadiusPx + (featureHeight/2)));
double headStart2X = mCenterPoint.getX() + (Math.cos(endRadians - arrowheadSize) * (featureRadiusPx));
double headStart2Y = mCenterPoint.getY() + (Math.sin(endRadians - arrowheadSize) * (featureRadiusPx));
List arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // rx
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(1f); // sweep-flag
arcNumbers.add((float)headStart1X);
arcNumbers.add((float)headStart1Y);
SvgPathEllipticalArcCmd outerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx)); // rx
arcNumbers.add((float) (featureRadiusPx)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(0f); // sweep-flag
arcNumbers.add((float)start1X);
arcNumbers.add((float)start1Y);
SvgPathEllipticalArcCmd innerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
Color color = mFeatureColorMap.get(inFeature.name());
if (null == color)
{
color = mDefaultFeatureColor;
}
g.addPath()
.addStyle(mSettings.getFeatureStyle())
.setFill(color)
.addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Double(start1X, start1Y)))
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(start2X, start2Y)))
.addPathCommand(outerArc)
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(tipX, tipY)))
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(headStart2X, headStart2Y)))
.addPathCommand(innerArc)
.addPathCommand(new SvgPathClosePathCmd());
// Label
g.addSubtag(generateFeatureLabel(inFeature, startRadians, endRadians - arrowheadSize));
// Tooltip
g.addSubtag(generateTooltip(inFeature));
return g;
}
//---------------------------------------------------------------------------
private SvgNode generateRevFrameFeature(SeqFeature inFeature)
{
Range location = inFeature.getLocation().toIntRange();
double featureRadiusPx = mBackboneRadiusPx + 3;
int featureHeight = 20;
SvgGroup g = new SvgGroup();
double arrowheadSize = 0.05; // in radians
double startRadians = bpToRadians(location.getStart());
double endRadians = bpToRadians(location.getEnd());
if (endRadians - startRadians < arrowheadSize)
{
arrowheadSize = (endRadians - startRadians);
}
double tipX = mCenterPoint.getX() + (Math.cos(startRadians) * (featureRadiusPx + (featureHeight/2f)));
double tipY = mCenterPoint.getY() + (Math.sin(startRadians) * (featureRadiusPx + (featureHeight/2f)));
double headStartTopX = mCenterPoint.getX() + (Math.cos(startRadians + arrowheadSize) * (featureRadiusPx + featureHeight));
double headStartTopY = mCenterPoint.getY() + (Math.sin(startRadians + arrowheadSize) * (featureRadiusPx + featureHeight));
double endTopX = mCenterPoint.getX() + (Math.cos(endRadians) * (featureRadiusPx + featureHeight));
double endTopY = mCenterPoint.getY() + (Math.sin(endRadians) * (featureRadiusPx + featureHeight));
double endBottomX = mCenterPoint.getX() + (Math.cos(endRadians) * (featureRadiusPx));
double endBottomY = mCenterPoint.getY() + (Math.sin(endRadians) * (featureRadiusPx));
double headStartBottomX = mCenterPoint.getX() + (Math.cos(startRadians + arrowheadSize) * featureRadiusPx);
double headStartBottomY = mCenterPoint.getY() + (Math.sin(startRadians + arrowheadSize) * featureRadiusPx);
List arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // rx
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(1f); // sweep-flag
arcNumbers.add((float)endTopX);
arcNumbers.add((float)endTopY);
SvgPathEllipticalArcCmd outerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx)); // rx
arcNumbers.add((float) (featureRadiusPx)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(0f); // sweep-flag
arcNumbers.add((float)headStartBottomX);
arcNumbers.add((float)headStartBottomY);
SvgPathEllipticalArcCmd innerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
Color color = mFeatureColorMap.get(inFeature.name());
if (null == color)
{
color = mDefaultFeatureColor;
}
g.addPath()
.addStyle(mSettings.getFeatureStyle())
.setFill(color)
.addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Double(tipX, tipY)))
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(headStartTopX, headStartTopY)))
.addPathCommand(outerArc)
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(endBottomX, endBottomY)))
.addPathCommand(innerArc)
.addPathCommand(new SvgPathClosePathCmd());
// Label
g.addSubtag(generateFeatureLabel(inFeature, startRadians + arrowheadSize, endRadians));
// Tooltip
g.addSubtag(generateTooltip(inFeature));
return g;
}
//---------------------------------------------------------------------------
private SvgNode generateDirectionlessFeature(SeqFeature inFeature)
{
Range location = inFeature.getLocation().toIntRange();
double featureRadiusPx = mBackboneRadiusPx + 3;
int featureHeight = 20;
SvgGroup g = new SvgGroup();
double startRadians = bpToRadians(location.getStart());
double endRadians = bpToRadians(location.getEnd());
double startTopX = mCenterPoint.getX() + (Math.cos(startRadians) * (featureRadiusPx + featureHeight));
double startTopY = mCenterPoint.getY() + (Math.sin(startRadians) * (featureRadiusPx + featureHeight));
double endTopX = mCenterPoint.getX() + (Math.cos(endRadians) * (featureRadiusPx + featureHeight));
double endTopY = mCenterPoint.getY() + (Math.sin(endRadians) * (featureRadiusPx + featureHeight));
double endBottomX = mCenterPoint.getX() + (Math.cos(endRadians) * (featureRadiusPx));
double endBottomY = mCenterPoint.getY() + (Math.sin(endRadians) * (featureRadiusPx));
double startBottomX = mCenterPoint.getX() + (Math.cos(startRadians) * featureRadiusPx);
double startBottomY = mCenterPoint.getY() + (Math.sin(startRadians) * featureRadiusPx);
List arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // rx
arcNumbers.add((float) (featureRadiusPx + featureHeight)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(1f); // sweep-flag
arcNumbers.add((float)endTopX);
arcNumbers.add((float)endTopY);
SvgPathEllipticalArcCmd outerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx)); // rx
arcNumbers.add((float) (featureRadiusPx)); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((endRadians - startRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(0f); // sweep-flag
arcNumbers.add((float)startBottomX);
arcNumbers.add((float)startBottomY);
SvgPathEllipticalArcCmd innerArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
Color color = mFeatureColorMap.get(inFeature.name());
if (null == color)
{
color = mDefaultFeatureColor;
}
g.addPath()
.addStyle(mSettings.getFeatureStyle())
.setFill(color)
.addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Double(startBottomX, startBottomY)))
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(startTopX, startTopY)))
.addPathCommand(outerArc)
.addPathCommand(new SvgPathLineToCmd().addPoint(new Point2D.Double(endBottomX, endBottomY)))
.addPathCommand(innerArc)
.addPathCommand(new SvgPathClosePathCmd());
// Label
g.addSubtag(generateFeatureLabel(inFeature, startRadians, endRadians));
// Tooltip
g.addSubtag(generateTooltip(inFeature));
return g;
}
//---------------------------------------------------------------------------
private SvgTitle generateTooltip(SeqFeature inFeature)
{
StringBuilderPlus tooltip = new StringBuilderPlus().setDelimiter("\n");
List notes = inFeature.getQualifiers(GenBankFeatureQualifierName.note.name());
if (CollectionUtil.hasValues(notes))
{
tooltip.delimitedAppend(notes.get(0).getValue());
}
List functions = inFeature.getQualifiers(GenBankFeatureQualifierName.function.name());
if (CollectionUtil.hasValues(functions))
{
tooltip.delimitedAppend("(" + functions.get(0).getValue() + ")");
}
tooltip.delimitedAppend(inFeature.getLocation().toString());
return new SvgTitle(tooltip);
}
//---------------------------------------------------------------------------
private SvgNode generateFeatureLabel(SeqFeature inFeature, double inLeftRadians, double inRightRadians)
{
double featureRadiusPx = mBackboneRadiusPx + 3;
int featureHeightPx = mSettings.getFeatureHeight().toInt(GfxUnits.pixels);
SvgGroup g = new SvgGroup();
String label = null;
List productQualifiers = inFeature.getQualifiers(GenBankFeatureQualifierName.product.name());
if (CollectionUtil.hasValues(productQualifiers))
{
label = productQualifiers.get(0).getValue();
}
else
{
List noteQualifiers = inFeature.getQualifiers(GenBankFeatureQualifierName.note.name());
if (CollectionUtil.hasValues(noteQualifiers))
{
label = noteQualifiers.get(0).getValue();
label = StringUtil.replaceAll(label, "origin of replication", "origin");
}
}
if (StringUtil.isSet(label))
{
TextLayout layout = new TextLayout(label, mSettings.getFeatureLabelFont(), sFRC);
Rectangle2D bounds = layout.getBounds();
double labelHeight = bounds.getHeight();
double labelWidth = bounds.getWidth();
String pathId = "featureLabelPath" + (mPathIdSrc++);
double labelStartX = mCenterPoint.getX() + (Math.cos(inLeftRadians) * (featureRadiusPx + (featureHeightPx/4)));
double labelStartY = mCenterPoint.getY() + (Math.sin(inLeftRadians) * (featureRadiusPx + (featureHeightPx/4)));
double labelEndX = mCenterPoint.getX() + (Math.cos(inRightRadians) * (featureRadiusPx + (featureHeightPx/4)));
double labelEndY = mCenterPoint.getY() + (Math.sin(inRightRadians) * (featureRadiusPx + (featureHeightPx/4)));
double arcLength = featureRadiusPx * (inRightRadians - inLeftRadians);
if (labelWidth > arcLength - 25)
{
// Attach the label via a stem
// Stem
double centerRadians = inLeftRadians + (inRightRadians - inLeftRadians) / 2;
double x1 = mCenterPoint.getX() + (Math.cos(centerRadians) * (featureRadiusPx + featureHeightPx));
double y1 = mCenterPoint.getY() + (Math.sin(centerRadians) * (featureRadiusPx + featureHeightPx));
double x2 = mCenterPoint.getX() + (Math.cos(centerRadians) * (featureRadiusPx + featureHeightPx + 20));
double y2 = mCenterPoint.getY() + (Math.sin(centerRadians) * (featureRadiusPx + featureHeightPx + 20));
g.addLine(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)).addClass(STEM).addStyle(mSettings.getFeatureStyle());
// Add label
double x = x2;
double y = y2;
String[] labelLines = StringUtil.lines(StringUtil.wrap(label, 15));
if (labelLines.length > 1)
{
String longestLine = null;
for (String line : labelLines)
{
if (null == longestLine
|| line.length() > longestLine.length())
{
longestLine = line;
}
}
layout = new TextLayout(longestLine, mSettings.getFeatureLabelFont(), sFRC);
bounds = layout.getBounds();
labelHeight = bounds.getHeight() * labelLines.length;
labelWidth = bounds.getWidth();
}
Range featureRange = inFeature.getLocation().toIntRange();
int midpoint = featureRange.getStart() - 1 + (featureRange.getEnd() - featureRange.getStart() + 1) / 2;
PlasmidLabel plasmidLabel = new PlasmidLabel(midpoint);
g.addSubtag(plasmidLabel);
/*
// Adjust the position
if (centerRadians >= 0
&& centerRadians < 0.785398)
{
// 0 (3 o'clock) to 45 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x += labelWidth * .75;
y += labelHeight / 2;
}
else
{
y += labelHeight + 5;
}
}
else if (centerRadians >= 0.785398
&& centerRadians < 1.5708)
{
// 45 to 90 degrees (6 o'clock)
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x += labelWidth * .75;
y += labelHeight;
}
else
{
y += labelHeight + 5;
}
}
else if (centerRadians >= 1.5708
&& centerRadians < 2.35619)
{
// 90 degrees (6 o'clock) to 135 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x -= labelWidth * .75;
y += labelHeight / 2f;
}
else
{
x -= 15 + labelWidth;
y += labelHeight + 5;
}
}
else if (centerRadians >= 2.35619
&& centerRadians < Math.PI)
{
// 135 degrees to 180 degrees (9 o'clock)
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x -= labelWidth * .75;
y += labelHeight / 2f;
}
else
{
x -= 15 + labelWidth;
y += labelHeight + 5;
}
}
else if (centerRadians >= Math.PI
&& centerRadians < 4.71239)
{
// 180 degrees (9 o'clock) to 270 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x -= labelWidth / 2f + 10;
y -= labelHeight - 5;
}
else
{
x -= labelWidth / 2f + 10;
y -= 5;
}
}
else
{
// 270 to 360 degrees (Noon to 3 o'clock)
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x += labelWidth * .75;
y -= labelHeight - 5;
}
}
*/
// Adjust the position
if (plasmidLabel.getQuadrant().equals(Quadrant.I))
{
// 0 to 90 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x += labelWidth * .75;
y -= labelHeight - 5;
}
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.IV))
{
// 90 to 180 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x += labelWidth * .75;
y += labelHeight;
}
else
{
y += labelHeight + 5;
}
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.III))
{
// 180 to 270 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x -= labelWidth * .75;
y += labelHeight / 2f;
}
else
{
x -= 15 + labelWidth;
y += labelHeight + 5;
}
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.II))
{
// 270 to 360 degrees
if (labelLines.length > 1)
{
// Multi-line labels are center-aligned
// so they need to be treated differently
x -= labelWidth / 2f + 10;
y -= labelHeight - 5;
}
else
{
x -= labelWidth / 2f + 10;
y -= 5;
}
}
if (1 == labelLines.length) // Single line?
{
plasmidLabel.setContent(label)
.setTextAnchor(SvgTextAnchor.start)
.setVerticalAlign(SvgVerticalAlign.first)
.setX((int)x)
.setY((int)y);
}
else
{
plasmidLabel.setTextAnchor(SvgTextAnchor.middle);
for (int i = 0; i < labelLines.length; i++)
{
String line = labelLines[i];
SvgTSpan tspan = plasmidLabel.addTSpan(line)
.setX((int) x);
if (0 == i)
{
tspan.setY((int)(y - labelHeight/2));
}
else
{
tspan.setDy(12);
}
}
}
plasmidLabel.setFont(mSettings.getFeatureLabelFont());
}
else
{
// Display the label within the path
List arcNumbers = new ArrayList<>(7);
arcNumbers.add((float) (featureRadiusPx + (featureHeightPx / 4))); // rx
arcNumbers.add((float) (featureRadiusPx + (featureHeightPx / 4))); // ry
arcNumbers.add(0f); // x-rotation
arcNumbers.add((inRightRadians - inLeftRadians) > Math.PI ? 1f : 0f); // large-arc-sweep-flag
arcNumbers.add(1f); // sweep-flag
arcNumbers.add((float) labelEndX);
arcNumbers.add((float) labelEndY);
SvgPathEllipticalArcCmd labelArc = new SvgPathEllipticalArcCmd().setRawNumbers(arcNumbers);
g.addPath()
.setId(pathId)
.setFill(null)
.setStroke(null)
.addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Double(labelStartX, labelStartY)))
.addPathCommand(labelArc);
SvgText labelNode = g.addText()
.setFont(mSettings.getFeatureLabelFont())
.setTextAnchor(SvgTextAnchor.middle)
.setVerticalAlign(SvgVerticalAlign.first);
labelNode.addSubtag(new SvgTextPath(label).setHref("#" + pathId)
.setStartOffsetPct(50)
.setSpacing(SvgTextPathSpacing.auto)
.setMethod(SvgTextPathMethod.align));
}
}
return g;
}
//---------------------------------------------------------------------------
private SvgNode generateRestrictionSites()
{
SvgGroup g = new SvgGroup().setId("RestrictionSites");
List restrictionEnzymes = mSettings.getRestrictionEnzymes();
if (CollectionUtil.hasValues(restrictionEnzymes))
{
List restrictionSites = new ArrayList<>();
// First, scan the plasmid with the specified restriction enzymes.
for (RestrictionEnzyme re : restrictionEnzymes)
{
List matches = re.matcher(mSeq).findAll();
if (CollectionUtil.hasValues(matches))
{
restrictionSites.addAll(matches);
}
}
if (CollectionUtil.hasValues(restrictionSites))
{
restrictionSites.sort(RestrictionEnzyme.LOCATION_COMPARATOR);
Font restrictionEnzymeFont = mSettings.getRestrictionEnzymeLabelFont();
int labelStemLengthPx = mSettings.getRestrictionEnzymeStemLength().toInt(GfxUnits.pixels);
for (RestrictionSite site : restrictionSites)
{
SvgGroup labelGroup = g.addGroup();
int[] fwdCutSiteIndices = site.getFwdStrandCutSiteIndices();
// Draw the label stem
double radians = bpToRadians(site.getSeqLocation().getStart());
double x1 = mCenterPoint.getX() + (Math.cos(radians) * (mBackboneRadiusPx));
double y1 = mCenterPoint.getY() + (Math.sin(radians) * (mBackboneRadiusPx));
double x2 = mCenterPoint.getX() + (Math.cos(radians) * (mBackboneRadiusPx + (labelStemLengthPx)));
double y2 = mCenterPoint.getY() + (Math.sin(radians) * (mBackboneRadiusPx + (labelStemLengthPx)));
labelGroup.addLine(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)).addClass(STEM).addStyle(mSettings.getRestrictionEnzymeStemStyle());
// Add label
StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(", ");
buffer.append(site.getRestrictionEnzyme().name());
buffer.append(" (");
buffer.append(StringUtil.generateLocalizedNumberString(fwdCutSiteIndices[0]));
if (fwdCutSiteIndices.length > 1)
{
buffer.delimitedAppend(StringUtil.generateLocalizedNumberString(fwdCutSiteIndices[1]));
}
buffer.append(")");
String label = buffer.toString();
PlasmidLabel plasmidLabel = new PlasmidLabel(site.getSeqLocation().getStart(), label);
// Determine label placement
TextLayout layout = new TextLayout(label, restrictionEnzymeFont, sFRC);
Rectangle2D bounds = layout.getBounds();
double labelHeight = bounds.getHeight();
double labelWidth = bounds.getWidth();
double x = x2;
double y = y2;
// Adjust the position
if (plasmidLabel.getQuadrant().equals(Quadrant.I))
{
// 0 to 90 degrees
x += 5;
y += labelHeight / 2f;
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.IV))
{
// 90 to 180 degrees
x += 5;
y += labelHeight / 2f;
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.III))
{
// 180 to 270 degrees
x -= (10 + labelWidth);
y += labelHeight;
}
else if (plasmidLabel.getQuadrant().equals(Quadrant.II))
{
// 270 to 360 degrees
x -= (10 + labelWidth);
// y += 5;
}
plasmidLabel
.setFont(restrictionEnzymeFont)
.setX((int)x)
.setY((int)y);
// Tooltip
plasmidLabel.addSubtag(new SvgTitle(label));
labelGroup.addSubtag(plasmidLabel);
}
}
}
return g;
}
//---------------------------------------------------------------------------
private void resolveOverlappingLabels(SVG inSVG)
{
// First, gather the stemmed labels.
List labelBoxes = inSVG.getSubtagsByAttribute(HTML.CLASS, STEMMED_LABEL, Recursion.ON);
if (CollectionUtil.hasValues(labelBoxes))
{
Collections.sort(labelBoxes);
int iterations = 0;
boolean overlapsFound = true;
while (overlapsFound
&& iterations < 2)
{
iterations++;
overlapsFound = false;
for (int i = 0; i < labelBoxes.size() - 1; i++)
{
PlasmidLabel labelBox1 = labelBoxes.get(i);
for (int j = i + 1; j < labelBoxes.size(); j++)
{
PlasmidLabel labelBox2 = labelBoxes.get(j);
// Do they overlap?
Rectangle2D boundsBox1 = labelBox1.getBoundsBox();
Rectangle2D boundsBox2 = labelBox2.getBoundsBox();
if (boundsBox1.intersects(boundsBox2))
{
overlapsFound = true;
Quadrant quadrant = labelBox1.getQuadrant();
// Note that the 'y' of the bounding box is the top of the box
// while the 'y' of the SVG text element is the bottom of the box.
PlasmidLabel labelToStay;
PlasmidLabel labelToMove = null;
Rectangle2D labelToStayBoundsBox;
Rectangle2D labelToMoveBoundsBox;
if (quadrant.equals(Quadrant.I))
{
// Try to move the higher label up and to the left.
labelToMove = labelBox1.getBpPosition() < labelBox2.getBpPosition() ? labelBox1 : labelBox2;
labelToStay = (labelToMove == labelBox1 ? labelBox2 : labelBox1);
labelToMoveBoundsBox = (labelToMove == labelBox1 ? boundsBox1 : boundsBox2);
labelToStayBoundsBox = (labelToStay == labelBox1 ? boundsBox1 : boundsBox2);
Rectangle2D newBoundsBox = new Rectangle((int) labelToMoveBoundsBox.getX() - 5,
(int) (labelToStayBoundsBox.getY() - labelToMoveBoundsBox.getHeight() - 2),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (hasCollision(labelBoxes, labelToMove, newBoundsBox))
{
// Try extending the stem
boolean newSiteFound = false;
SvgLine stem = (SvgLine) ((SvgGroup) labelToMove.getParentNode()).getSubtagsByAttribute(HTML.CLASS, STEM).get(0);
float initialStemLength = stem.length();
for (int extraStemLength = 5; extraStemLength <= 70; extraStemLength+=5)
{
double x = mCenterPoint.getX() + (Math.cos(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
double y = mCenterPoint.getY() + (Math.sin(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
Rectangle2D newBoundsBox2 = new Rectangle((int) x,
(int) (y - labelToMoveBoundsBox.getHeight() / 2f),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (! hasCollision(labelBoxes, labelToMove, newBoundsBox2, 2))
{
newSiteFound = true;
newBoundsBox = newBoundsBox2;
break;
}
}
if (! newSiteFound)
{
// Try moving the label below the other value
Rectangle2D newBoundsBox2 = new Rectangle((int) labelToMoveBoundsBox.getX() + 5,
(int) (labelToStayBoundsBox.getY() + labelToStayBoundsBox.getHeight() + 2),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (! hasCollision(labelBoxes, labelToMove, newBoundsBox2))
{
newBoundsBox = newBoundsBox2;
}
}
}
moveLabel(labelToMove, newBoundsBox);
}
else if (quadrant.equals(Quadrant.II))
{
labelToMove = labelBox1.getBpPosition() < labelBox2.getBpPosition() ? labelBox2 : labelBox1;
labelToMoveBoundsBox = (labelToMove == labelBox1 ? boundsBox1 : boundsBox2);
SvgLine stem = (SvgLine) ((SvgGroup) labelToMove.getParentNode()).getSubtagsByAttribute(HTML.CLASS, STEM).get(0);
float initialStemLength = stem.length();
for (int extraStemLength = 5; extraStemLength <= 50; extraStemLength+=5)
{
double x = mCenterPoint.getX() + (Math.cos(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
double y = mCenterPoint.getY() + (Math.sin(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
Rectangle2D newBoundsBox = new Rectangle((int) (x - labelToMoveBoundsBox.getWidth()),
(int) (y - labelToMoveBoundsBox.getHeight() / 2f),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (! hasCollision(labelBoxes, labelToMove, newBoundsBox, 2))
{
moveLabel(labelToMove, newBoundsBox);
break;
}
}
}
else if (quadrant.equals(Quadrant.III))
{
labelToMove = labelBox1.getBpPosition() < labelBox2.getBpPosition() ? labelBox1 : labelBox2;
labelToMoveBoundsBox = (labelToMove == labelBox1 ? boundsBox1 : boundsBox2);
SvgLine stem = (SvgLine) ((SvgGroup) labelToMove.getParentNode()).getSubtagsByAttribute(HTML.CLASS, STEM).get(0);
float initialStemLength = stem.length();
for (int extraStemLength = 5; extraStemLength <= 50; extraStemLength+=5)
{
double x = mCenterPoint.getX() + (Math.cos(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
double y = mCenterPoint.getY() + (Math.sin(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
Rectangle2D newBoundsBox = new Rectangle((int) (x - labelToMoveBoundsBox.getWidth()),
(int) (y + labelToMoveBoundsBox.getHeight() / 2f),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (! hasCollision(labelBoxes, labelToMove, newBoundsBox, 2))
{
moveLabel(labelToMove, newBoundsBox);
break;
}
}
}
else if (quadrant.equals(Quadrant.IV))
{
labelToMove = labelBox1.getBpPosition() < labelBox2.getBpPosition() ? labelBox2 : labelBox1;
labelToMoveBoundsBox = (labelToMove == labelBox1 ? boundsBox1 : boundsBox2);
SvgLine stem = (SvgLine) ((SvgGroup) labelToMove.getParentNode()).getSubtagsByAttribute(HTML.CLASS, STEM).get(0);
float initialStemLength = stem.length();
for (int extraStemLength = 5; extraStemLength <= 50; extraStemLength+=5)
{
double x = mCenterPoint.getX() + (Math.cos(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
double y = mCenterPoint.getY() + (Math.sin(labelToMove.getRadians()) * (mBackboneRadiusPx + (initialStemLength + extraStemLength)));
Rectangle2D newBoundsBox = new Rectangle((int) x,
(int) (y + labelToMoveBoundsBox.getHeight() / 2f),
(int) labelToMoveBoundsBox.getWidth(),
(int) labelToMoveBoundsBox.getHeight());
if (! hasCollision(labelBoxes, labelToMove, newBoundsBox, 2))
{
moveLabel(labelToMove, newBoundsBox);
break;
}
}
}
// Adjust the stem placement
SvgLine stem = (SvgLine) ((SvgGroup) labelToMove.getParentNode()).getSubtagsByAttribute(HTML.CLASS, STEM).get(0);
labelToMoveBoundsBox = labelToMove.getBoundsBox();
if (quadrant.equals(Quadrant.I))
{
stem.setX2((int) labelToMoveBoundsBox.getX() - 2)
.setY2((int) labelToMoveBoundsBox.getY() + (int) (labelToMoveBoundsBox.getHeight() * 0.75));
}
else if (quadrant.equals(Quadrant.II))
{
stem.setX2((int) (labelToMoveBoundsBox.getX() + labelToMoveBoundsBox.getWidth()))
.setY2((int) (labelToMoveBoundsBox.getY() + labelToMoveBoundsBox.getHeight()));
}
else if (quadrant.equals(Quadrant.III))
{
stem.setX2((int) (labelToMoveBoundsBox.getX() + labelToMoveBoundsBox.getWidth()))
.setY2((int) (labelToMoveBoundsBox.getY()));
}
else if (quadrant.equals(Quadrant.IV))
{
stem.setX2((int) labelToMoveBoundsBox.getX())
.setY2((int) (labelToMoveBoundsBox.getY()));
}
}
}
}
}
}
}
//---------------------------------------------------------------------------
private void moveLabel(PlasmidLabel inLabel, Rectangle2D inNewBoundsBox)
{
SvgTextAnchor textAnchor = SvgTextAnchor.start;
if (inLabel.hasAttribute(SvgAttr.textAnchor))
{
textAnchor = SvgTextAnchor.valueOf(inLabel.getAttributeValue(SvgAttr.textAnchor));
}
float x = (float) inNewBoundsBox.getX();
if (textAnchor.equals(SvgTextAnchor.middle))
{
x += inNewBoundsBox.getWidth() / 2f;
}
// TSpans?
List textSpans = inLabel.getSubtagsByName(SVG.tspan);
if (CollectionUtil.hasValues(textSpans))
{
for (SvgTSpan tSpan : textSpans)
{
if (tSpan.hasAttribute(SvgAttr.x))
{
tSpan.setX(x);
}
if (tSpan.hasAttribute(SvgAttr.y))
{
tSpan.setY((int) (inNewBoundsBox.getY() + inNewBoundsBox.getHeight() / textSpans.size()));
}
}
}
else
{
inLabel.setX((int) x);
inLabel.setY((int) (inNewBoundsBox.getY() + inNewBoundsBox.getHeight()));
}
}
//---------------------------------------------------------------------------
private boolean hasCollision(List inLabels, PlasmidLabel inLabel, Rectangle2D inNewBoundsBox)
{
return hasCollision(inLabels, inLabel, inNewBoundsBox, 0);
}
//---------------------------------------------------------------------------
private boolean hasCollision(List inLabels, PlasmidLabel inLabel, Rectangle2D inNewBoundsBox, int inMargin)
{
boolean hasCollision = false;
Rectangle2D boundsBox = inNewBoundsBox;
if (inMargin > 0)
{
boundsBox = new Rectangle((int) inNewBoundsBox.getX() - inMargin,
(int) inNewBoundsBox.getY() - inMargin,
(int) inNewBoundsBox.getWidth() + (2 * inMargin),
(int) inNewBoundsBox.getHeight() + (2 * inMargin));
}
for (PlasmidLabel label : inLabels)
{
if (label != inLabel
&& label.getBoundsBox().intersects(boundsBox))
{
hasCollision = true;
break;
}
}
return hasCollision;
}
//**************************************************************************
// INNER CLASS
//**************************************************************************
private class PlasmidLabel extends SvgText implements Comparable
{
private int mBpPosition;
private Double mRadians;
private Quadrant mQuadrant;
//------------------------------------------------------------------------
public PlasmidLabel(int inBpPosition, String inContent)
{
super(inContent);
mBpPosition = inBpPosition;
addClass(STEMMED_LABEL);
}
//------------------------------------------------------------------------
public PlasmidLabel(int inBpPosition)
{
super();
mBpPosition = inBpPosition;
addClass(STEMMED_LABEL);
}
//---------------------------------------------------------------------------
@Override
public boolean equals(Object inObj2)
{
return (0 == compareTo(inObj2));
}
//---------------------------------------------------------------------------
@Override
public int hashCode()
{
return mBpPosition;
}
//---------------------------------------------------------------------------
@Override
public int compareTo(Object inObj2)
{
int result = -1;
if (inObj2 instanceof PlasmidLabel)
{
result = CompareUtil.compare(mBpPosition, ((PlasmidLabel) inObj2).mBpPosition);
}
return result;
}
//------------------------------------------------------------------------
public int getBpPosition()
{
return mBpPosition;
}
//------------------------------------------------------------------------
public Quadrant getQuadrant()
{
if (null == mQuadrant)
{
double radians = getRadians();
if (radians < 0
&& radians >= - 1.57)
{
// 0 to 90 degrees
mQuadrant = Quadrant.I;
}
else if (radians >= 0
&& radians < 1.57)
{
// 90 to 180 degrees
mQuadrant = Quadrant.IV;
}
else if (radians >= 1.57
&& radians < 3.14)
{
// 180 to 270 degrees
mQuadrant = Quadrant.III;
}
else
{
// 270 to 360 degrees
mQuadrant = Quadrant.II;
}
}
return mQuadrant;
}
//------------------------------------------------------------------------
public double getRadians()
{
if (null == mRadians)
{
mRadians = (mBpPosition * ((Math.PI * 2.0) / mSeq.length())) - ((Math.PI / 180.0) * getOrigin());
}
return mRadians;
}
}
}