/*
* Copyright (c) 2009 The Jackson Laboratory
*
* This software was developed by Gary Churchill's Lab at The Jackson
* Laboratory (see http://research.jax.org/faculty/churchill).
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.qtl.scan.gui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JToolTip;
import javax.swing.SwingUtilities;
import org.jax.analyticgraph.framework.AbstractGraph2DWithAxes;
import org.jax.analyticgraph.framework.GraphCoordinateConverter;
import org.jax.analyticgraph.framework.SimpleGraphCoordinateConverter;
import org.jax.analyticgraph.graph.AxisDescription;
import org.jax.analyticgraph.graph.RegularIntervalAxisDescription;
import org.jax.analyticgraph.graph.AxisDescription.AxisType;
import org.jax.qtl.Constants;
import org.jax.qtl.action.OpenMgdUrlAction;
import org.jax.qtl.cross.Cross;
import org.jax.qtl.cross.GeneticMap;
import org.jax.qtl.cross.GeneticMarker;
import org.jax.qtl.cross.gui.MarkerAxisDescription;
import org.jax.qtl.cross.gui.MarkerPositionManager;
import org.jax.qtl.cross.gui.ShowEffectPlotAction;
import org.jax.qtl.scan.ScanOneMarkerSignificanceValues;
import org.jax.qtl.scan.ScanOneResult;
import org.jax.qtl.scan.ScanOneThreshold;
import org.jax.qtl.scan.gui.ScanOneInterval.IntervalPoint;
import org.jax.r.jriutilities.RInterfaceFactory;
import org.jax.util.datastructure.SequenceUtilities;
import org.jax.util.gui.MessageDialogUtilities;
/**
* For graphing marker values
* @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
*/
public class ScanOneGraph extends AbstractGraph2DWithAxes
{
private static final String DEFAULT_Y_AXIS_NAME_SUFFIX = "LOD Scores";
private static final int DEFAULT_Y_AXIS_TICK_COUNT = 10;
private static final int DEFAULT_Y_AXIS_SIGNIFICANT_DIGITS = 2;
private static final String DEFAULT_X_AXIS_NAME = "Genetic Markers";
private static final double[] DEFAULT_ALPHA_THRESHOLDS = {
0.05,
0.10,
0.63};
/**
* our mouse motion listener
*/
private MouseMotionListener containerComponentMotionListener =
new MouseMotionListener()
{
public void mouseDragged(MouseEvent event)
{
// no-op
}
public void mouseMoved(MouseEvent event)
{
ScanOneGraph.this.containerComponentMouseMoved(event);
}
};
/**
* our mouse listener
*/
private MouseListener containerComponentMouseListener =
new MouseListener()
{
public void mouseClicked(MouseEvent event)
{
if(event.isPopupTrigger())
{
ScanOneGraph.this.showPopupMenu(event.getPoint());
}
}
public void mousePressed(MouseEvent event)
{
if(event.isPopupTrigger())
{
ScanOneGraph.this.showPopupMenu(event.getPoint());
}
}
public void mouseReleased(MouseEvent event)
{
if(event.isPopupTrigger())
{
ScanOneGraph.this.showPopupMenu(event.getPoint());
}
}
public void mouseEntered(MouseEvent e)
{
// no-op
}
public void mouseExited(MouseEvent e)
{
ScanOneGraph.this.getContainerComponent().remove(
ScanOneGraph.this.toolTip);
}
};
/**
* our logger
*/
private static final Logger LOG = Logger.getLogger(
ScanOneGraph.class.getName());
private final JToolTip toolTip;
private volatile Point lastMousePosition = new Point();
/**
* for figuring our where the markers are positioned
*/
private final MarkerPositionManager markerPositionManager;
private volatile List<List<ScanOneMarkerSignificanceValues>> markerSignificanceValues;
private volatile ScanOneMarkerSignificanceValues markerSignificanceValueToHighlight;
private volatile ScanOneResult scanOneResult;
private static final Stroke CHROMOSOME_SEPARATOR_STROKE = new BasicStroke(
0.5F,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0F,
new float[] {6.0F, 3.0F},
0.0F);
private static final Stroke ALPHA_THRESHOLD_STROKE = new BasicStroke(
0.5F,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0F,
new float[] {4.0F, 4.0F},
0.0F);
private static final Stroke INTERVAL_CENTER_STROKE = new BasicStroke(
0.5F,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0F,
new float[] {1.0F, 4.0F},
0.0F);
private static final Stroke INTERVAL_BOUNDARY_STROKE = new BasicStroke(
0.5F,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
// TODO when we go to 6.0 we can use type safe "SansSerif"
private static final Font GRAPH_TEXT_FONT =
new Font("SansSerif", Font.PLAIN, 10);
/**
* the cursor offset to use
*/
// TODO ideally we'd be able to query this but it doesn't look like that's
// possible, so we should at least move this to a config file
private static final int CURSOR_Y_OFFSET = 16;
private final GeneticMap[] geneticMaps;
private volatile MarkerAxisDescription xAxisDescription;
private volatile RegularIntervalAxisDescription yAxisDescription;
private volatile ScanOneThreshold[] thresholdsToRender;
private final ScanOneIntervalCommandBuilder scanOneIntervalCommandBuilder;
private final PropertyChangeListener scanOneIntervalPropertyListener =
new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
ScanOneGraph.this.scanOneIntervalChanged();
}
};
private String lodColumnName;
private volatile boolean showThresholdLabel = true;
/**
* Constructor
* @param scanOneResult
* the scanone result to show
* @param markerPositionManager
* the marker position manager to use
* @param geneticMaps
* the genetic maps to use
*/
public ScanOneGraph(
ScanOneResult scanOneResult,
MarkerPositionManager markerPositionManager,
GeneticMap[] geneticMaps)
{
super(new SimpleGraphCoordinateConverter(
0, 0,
1, 1));
this.markerPositionManager = markerPositionManager;
this.geneticMaps = geneticMaps;
this.toolTip = new JToolTip();
this.scanOneIntervalCommandBuilder =
new ScanOneIntervalCommandBuilder();
this.scanOneIntervalCommandBuilder.setScanOneResult(
scanOneResult);
this.scanOneIntervalCommandBuilder.addPropertyChangeListener(
this.scanOneIntervalPropertyListener);
this.lodColumnName = scanOneResult.getSignificanceValueColumnNames()[0];
this.setScanOneResult(scanOneResult);
}
/**
* Getter for determining whether or not to show the threshold labels
* @return
* true iff we should show the label
*/
public boolean getShowThresholdLabel()
{
return this.showThresholdLabel;
}
/**
* Setter for determining whether or not to show the threshold label
* @param showThresholdLabel
* if true we should show the label
*/
public void setShowThresholdLabel(boolean showThresholdLabel)
{
this.showThresholdLabel = showThresholdLabel;
this.getContainerComponent().repaint();
}
/**
* respond to a change in the interval
*/
private void scanOneIntervalChanged()
{
this.getContainerComponent().repaint();
}
/**
* Show the popup menu at the given location
* @param popupPoint
* the point that we're showing the popup menu at
*/
private void showPopupMenu(Point popupPoint)
{
if(this.getGraphCoordinateConverter().isPixelPointInBounds(popupPoint))
{
ScanOneResult scanOneResult = this.scanOneResult;
Cross parentCross = scanOneResult.getParentCross();
String phenotypeName = scanOneResult.findScannedPhenotypeNameForScanColumn(
this.lodColumnName);
ScanOneMarkerSignificanceValues closestMarkerValue =
this.getClosestMarkerSignificanceValue(popupPoint);
GeneticMarker closestTrueMarker =
this.getClosestTrueMarker(popupPoint);
if(parentCross != null && phenotypeName != null)
{
JPopupMenu popupMenu = new JPopupMenu();
if(closestTrueMarker != null)
{
popupMenu.add(new ShowEffectPlotAction(
parentCross,
phenotypeName,
closestTrueMarker));
}
if(closestMarkerValue != null)
{
popupMenu.add(new OpenMgdUrlAction(closestMarkerValue.getMarker()));
}
if(closestTrueMarker != null || closestMarkerValue != null)
{
popupMenu.show(
this.getContainerComponent(),
popupPoint.x,
popupPoint.y);
}
}
else
{
LOG.warning(
"can't show effect plot since we dont have all " +
"of the data we need: parentCross=" + parentCross +
" phenotypeName=" + phenotypeName +
" closestMarker=" + closestMarkerValue);
}
}
}
/**
* Get the true marker closest to the given point (pseudo markers
* are not considered)
* @param point
* the point (in pixel units)
* @return
* the marker or null if we can't find the closest marker
*/
private GeneticMarker getClosestTrueMarker(Point point)
{
double pointGraphX =
this.getGraphCoordinateConverter().convertJava2DXCoordinateToGraphXCoordinate(
point.getX());
GeneticMarker closestMarker = null;
double closestDistance = Double.MAX_VALUE;
for(GeneticMap currMap: this.geneticMaps)
{
for(GeneticMarker currMarker: currMap.getMarkerPositions())
{
Double currMarkerPosition =
this.markerPositionManager.getMarkerPositionInGraphUnits(
currMarker);
if(currMarkerPosition != null)
{
double currDistance = Math.abs(
pointGraphX - currMarkerPosition);
if(closestMarker == null || currDistance < closestDistance)
{
closestDistance = currDistance;
closestMarker = currMarker;
}
}
else
{
LOG.warning(
"couldn't find marker position for: " + currMarker);
}
}
}
return closestMarker;
}
/**
* Respond to a mouse move event
* @param event
* the event
*/
private void containerComponentMouseMoved(MouseEvent event)
{
this.lastMousePosition = event.getPoint();
GraphCoordinateConverter coordConverter =
this.getGraphCoordinateConverter();
JComponent container = this.getContainerComponent();
if(coordConverter.isPixelPointInBounds(event.getPoint()))
{
if(this.toolTip.getParent() == null)
{
container.add(this.toolTip);
}
ScanOneMarkerSignificanceValues closestMarker =
this.getClosestMarkerSignificanceValue(
event.getPoint());
if(this.markerSignificanceValueToHighlight != closestMarker)
{
this.markerSignificanceValueToHighlight = closestMarker;
container.repaint();
}
this.updateToolTipLayout();
}
else
{
container.remove(this.toolTip);
if(this.markerSignificanceValueToHighlight != null)
{
this.markerSignificanceValueToHighlight = null;
this.getContainerComponent().repaint();
}
}
}
/**
* Update the layout of the tooltip
*/
private void updateToolTipLayout()
{
JComponent container = this.getContainerComponent();
// if the tool tip goes off the right edge of the screen, move it to the
// left side of the cursor
if(this.lastMousePosition.x + this.toolTip.getPreferredSize().width >
container.getWidth())
{
this.toolTip.setLocation(
this.lastMousePosition.x - this.toolTip.getPreferredSize().width,
this.lastMousePosition.y + CURSOR_Y_OFFSET);
}
else
{
this.toolTip.setLocation(
this.lastMousePosition.x,
this.lastMousePosition.y + CURSOR_Y_OFFSET);
}
this.toolTip.setSize(this.toolTip.getPreferredSize());
}
/**
* Get the marker value that's closest to the given point
* @param point
* the point
* @return
* the marker significance values closest to the given point
*/
private ScanOneMarkerSignificanceValues getClosestMarkerSignificanceValue(
Point point)
{
double pointGraphX =
this.getGraphCoordinateConverter().convertJava2DXCoordinateToGraphXCoordinate(
point.getX());
ScanOneMarkerSignificanceValues closestMarker = null;
double closestDistance = Double.MAX_VALUE;
if(this.markerSignificanceValues != null)
{
for(List<ScanOneMarkerSignificanceValues> currSigValsList:
this.markerSignificanceValues)
{
for(ScanOneMarkerSignificanceValues currSigVals: currSigValsList)
{
GeneticMarker currMarker = currSigVals.getMarker();
double currMarkerGraphX =
this.markerPositionManager.getMarkerPositionInGraphUnits(
currMarker);
double currMarkerDistance =
Math.abs(pointGraphX - currMarkerGraphX);
// are we closer than any other marker we've seen?
if(closestMarker == null || currMarkerDistance < closestDistance)
{
// do we care if the marker is true or imputed
closestMarker = currSigVals;
closestDistance = currMarkerDistance;
}
}
}
}
return closestMarker;
}
/**
* Getter for the result that we're plotting
* @return the scanOneResult
*/
public ScanOneResult getScanOneResult()
{
return this.scanOneResult;
}
/**
* Updater for the lod column
* @param lodColumnName the LOD column name to set
* @param lodColumnIndex the LOD column index to set
*/
public void updateLodColumn(String lodColumnName, int lodColumnIndex)
{
this.lodColumnName = lodColumnName;
this.scanOneIntervalCommandBuilder.setLodColumnIndex(lodColumnIndex);
this.setScanOneResult(this.scanOneResult);
this.getContainerComponent().repaint();
}
/**
* Getter for the LOD column name
* @return the LOD column name
*/
public String getLodColumnName()
{
return this.lodColumnName;
}
/**
* Setter for the result that we're plotting.
* @param scanOneResult the scanOneResult to set
*/
private void setScanOneResult(final ScanOneResult scanOneResult)
{
this.scanOneResult = scanOneResult;
this.markerSignificanceValues =
scanOneResult.getMarkerSignificanceValuesByChromosome(
this.lodColumnName);
if(scanOneResult.getPermutationsWereCalculated())
{
this.thresholdsToRender = scanOneResult.calculateThresholds(
DEFAULT_ALPHA_THRESHOLDS,
this.lodColumnName);
}
else
{
this.thresholdsToRender = null;
}
this.updateGraphDimensions();
}
/**
* Getter for the interval command builder.
* @return
* the command builder
*/
public ScanOneIntervalCommandBuilder getScanOneIntervalCommandBuilder()
{
return this.scanOneIntervalCommandBuilder;
}
/**
* update the dimensions of this graph
*/
private void updateGraphDimensions()
{
// The LOD scale should not be less than one
double maxLodValue = 1.0;
List<List<GeneticMarker>> allMarkers = new ArrayList<List<GeneticMarker>>(
this.markerSignificanceValues.size());
final List<GeneticMarker> infiniteMarkers = new ArrayList<GeneticMarker>();
final List<GeneticMarker> nanMarkers = new ArrayList<GeneticMarker>();
for(List<ScanOneMarkerSignificanceValues> currSigList:
this.markerSignificanceValues)
{
List<GeneticMarker> currMarkerList = new ArrayList<GeneticMarker>(
currSigList.size());
allMarkers.add(currMarkerList);
for(ScanOneMarkerSignificanceValues currMarkerSigValues: currSigList)
{
currMarkerList.add(currMarkerSigValues.getMarker());
double currLod =
currMarkerSigValues.getLodScore();
if(Double.isInfinite(currLod))
{
infiniteMarkers.add(currMarkerSigValues.getMarker());
}
else if(Double.isNaN(currLod))
{
nanMarkers.add(currMarkerSigValues.getMarker());
}
else if(maxLodValue < currLod)
{
maxLodValue = currLod;
}
}
}
if(!infiniteMarkers.isEmpty())
{
SwingUtilities.invokeLater(new Runnable()
{
/**
* {@inheritDoc}
*/
public void run()
{
MessageDialogUtilities.warn(
ScanOneGraph.this.getContainerComponent(),
"The following markers have infinite LOD scores: " +
SequenceUtilities.toString(infiniteMarkers, ", "),
"Found Infinite LOD Scores");
}
});
}
if(!nanMarkers.isEmpty())
{
SwingUtilities.invokeLater(new Runnable()
{
/**
* {@inheritDoc}
*/
public void run()
{
MessageDialogUtilities.warn(
ScanOneGraph.this.getContainerComponent(),
"The following markers have \"Not a Number\" " +
"LOD scores: " +
SequenceUtilities.toString(nanMarkers, ", "),
"Found Bad LOD Scores");
}
});
}
this.getGraphCoordinateConverter().updateGraphDimensions(
0, 0, 1, maxLodValue);
// update the axes
this.xAxisDescription = new MarkerAxisDescription(
this.getGraphCoordinateConverter(),
AxisType.X_AXIS,
DEFAULT_X_AXIS_NAME,
this.geneticMaps,
allMarkers);
String lodColumnName = this.getLodColumnName();
String yAxisName =
lodColumnName == null || lodColumnName.equalsIgnoreCase("lod") ?
DEFAULT_Y_AXIS_NAME_SUFFIX :
lodColumnName + " " + DEFAULT_Y_AXIS_NAME_SUFFIX;
this.yAxisDescription = new RegularIntervalAxisDescription(
this.getGraphCoordinateConverter(),
AxisType.Y_AXIS,
yAxisName,
DEFAULT_Y_AXIS_TICK_COUNT,
DEFAULT_Y_AXIS_SIGNIFICANT_DIGITS,
true);
}
/**
* {@inheritDoc}
*/
public void renderGraph(Graphics2D graphics2D)
{
List<List<ScanOneMarkerSignificanceValues>> markerSignificanceValues =
this.markerSignificanceValues;
if(markerSignificanceValues != null)
{
for(int i = 0; i < markerSignificanceValues.size(); i++)
{
List<ScanOneMarkerSignificanceValues> currSigList =
markerSignificanceValues.get(i);
// draw a separator line if this isn't the 1st chromosome
if(i != 0)
{
String currChromosomeName =
currSigList.get(0).getMarker().getChromosomeName();
if(currChromosomeName == null)
{
LOG.warning(
"cannot draw a separator since i don't know " +
"what the chromosome's name is");
}
else
{
double separatorPosition =
this.markerPositionManager.getChromosomeStartingPositionInGraphUnits(
currChromosomeName);
this.drawChromosomeSeparator(
graphics2D,
separatorPosition);
}
}
// iterate through the current list which represents all of
// the markers in a chromosome
ScanOneMarkerSignificanceValues prevMarkerSigValues = null;
for(ScanOneMarkerSignificanceValues currMarkerSigValues: currSigList)
{
if(prevMarkerSigValues != null)
{
this.connectMarkerValuesPairs(
graphics2D,
prevMarkerSigValues.getMarker(),
prevMarkerSigValues.getLodScore(),
currMarkerSigValues.getMarker(),
currMarkerSigValues.getLodScore());
}
prevMarkerSigValues = currMarkerSigValues;
}
}
ScanOneMarkerSignificanceValues markerSignificanceValueToHighlight =
this.markerSignificanceValueToHighlight;
if(markerSignificanceValueToHighlight == null)
{
this.getContainerComponent().remove(this.toolTip);
}
else
{
GeneticMarker markerToHighlight =
markerSignificanceValueToHighlight.getMarker();
this.drawMarkerHighlight(
graphics2D,
markerToHighlight);
StringBuffer tipText = new StringBuffer(
"<html>Marker Name: " + markerToHighlight.getMarkerName() + ":" +
"<p>Chromosome: " + markerToHighlight.getChromosomeName() +
"<p>Location (cM): " + markerToHighlight.getMarkerPositionCentimorgans() +
"<p>LOD Score: " + markerSignificanceValueToHighlight.getLodScore());
tipText.append("</html>");
this.toolTip.setTipText(tipText.toString());
this.updateToolTipLayout();
}
this.renderIntervals(
graphics2D,
this.scanOneIntervalCommandBuilder.getScanOneIntervals(
RInterfaceFactory.getRInterfaceInstance()));
this.drawThresholds(graphics2D);
}
else
{
if(LOG.isLoggable(Level.FINE))
{
LOG.fine("not doing anything for render since data is null");
}
}
}
/**
* render the intervals
* @param intervals
* the intervals
*/
private void renderIntervals(
Graphics2D graphics2D,
List<ScanOneInterval> intervals)
{
if(intervals != null)
{
for(ScanOneInterval interval: intervals)
{
String chromosomeName = interval.getChromosomeName();
this.renderIntervalLine(
graphics2D,
chromosomeName,
interval.getIntervalShape().getLeftFlankPoint(),
INTERVAL_BOUNDARY_STROKE);
this.renderIntervalLine(
graphics2D,
chromosomeName,
interval.getIntervalShape().getRightFlankPoint(),
INTERVAL_BOUNDARY_STROKE);
this.renderIntervalLine(
graphics2D,
chromosomeName,
interval.getIntervalShape().getPeakPoint(),
INTERVAL_CENTER_STROKE);
}
}
}
private void renderIntervalLine(
Graphics2D graphics2D,
String chromosomeName,
IntervalPoint intervalPoint,
Stroke stroke)
{
GraphCoordinateConverter coordConverter =
this.getGraphCoordinateConverter();
double yBottomPixels =
coordConverter.convertGraphYCoordinateToJava2DYCoordinate(0.0);
double yTopPixels =
coordConverter.convertGraphYCoordinateToJava2DYCoordinate(
intervalPoint.getLodScore());
double xGraphUnits = this.markerPositionManager.getPositionInGraphUnits(
chromosomeName,
intervalPoint.getPositionInCentimorgans());
double xPixels =
coordConverter.convertGraphXCoordinateToJava2DXCoordinate(
xGraphUnits);
Line2D intervalLine = new Line2D.Double(
xPixels, yBottomPixels,
xPixels, yTopPixels);
graphics2D.draw(stroke.createStrokedShape(intervalLine));
}
/**
* Draw all of the thresholds
* @param graphics2D
* the graphics context to use
*/
private void drawThresholds(Graphics2D graphics2D)
{
ScanOneThreshold[] thresholds = this.thresholdsToRender;
if(thresholds != null)
{
for(ScanOneThreshold threshold: thresholds)
{
this.drawThreshold(graphics2D, threshold);
}
}
}
/**
* Draw the given threshold value
* @param graphics2D
* the graphics context to write to
* @param threshold
* the threshold to render
*/
private void drawThreshold(
Graphics2D graphics2D,
ScanOneThreshold threshold)
{
GraphCoordinateConverter coordConverter =
this.getGraphCoordinateConverter();
double xAllChromosStartInPixels =
coordConverter.getAbsoluteXOffsetInPixels();
double xAllChromosStopInPixels =
xAllChromosStartInPixels +
coordConverter.getAbsoluteWidthInPixels();
if(threshold.getXChromosomePValuesAreSeparate())
{
double xChromoLodYInPixels = coordConverter.convertGraphYCoordinateToJava2DYCoordinate(
threshold.getXChromosomeLodValue());
double autoLodYInPixels = coordConverter.convertGraphYCoordinateToJava2DYCoordinate(
threshold.getAutosomeLodValue());
List<ScanOneMarkerSignificanceValues> xChromoSigValues =
this.markerSignificanceValues.get(
this.markerSignificanceValues.size() - 1);
String xChromosomeName =
xChromoSigValues.get(0).getMarker().getChromosomeName();
double xChromoXStartInGraphUnits =
this.markerPositionManager.getChromosomeStartingPositionInGraphUnits(
xChromosomeName);
double xChromoXStartInPixels =
coordConverter.convertGraphXCoordinateToJava2DXCoordinate(
xChromoXStartInGraphUnits);
Line2D xChromoAlphaThresholdLine = new Line2D.Double(
xChromoXStartInPixels, xChromoLodYInPixels,
xAllChromosStopInPixels, xChromoLodYInPixels);
Line2D autoAlphaThresholdLine = new Line2D.Double(
xAllChromosStartInPixels, autoLodYInPixels,
xChromoXStartInPixels, autoLodYInPixels);
graphics2D.draw(ScanOneGraph.ALPHA_THRESHOLD_STROKE.createStrokedShape(
xChromoAlphaThresholdLine));
graphics2D.draw(ScanOneGraph.ALPHA_THRESHOLD_STROKE.createStrokedShape(
autoAlphaThresholdLine));
if(this.getShowThresholdLabel())
{
String xChromoLabelString =
"X Chromosome \u03B1 Threshold: " +
Constants.THREE_DIGIT_FORMATTER.format(threshold.getAlphaThreshold());
String autoChromoLabelString =
"Autosome \u03B1 Threshold: " +
Constants.THREE_DIGIT_FORMATTER.format(threshold.getAlphaThreshold());
Shape xChromoLabelShape = this.createGraphTextShape(
graphics2D,
xChromoLabelString);
Rectangle2D xChromoLabelBounds = xChromoLabelShape.getBounds2D();
AffineTransform xChromoAffTrans = new AffineTransform();
xChromoAffTrans.translate(
xAllChromosStopInPixels - xChromoLabelBounds.getWidth() - 1,
xChromoLodYInPixels - 1.0);
xChromoLabelShape = xChromoAffTrans.createTransformedShape(
xChromoLabelShape);
graphics2D.fill(xChromoLabelShape);
Shape autoLabelShape = this.createGraphTextShape(
graphics2D,
autoChromoLabelString);
Rectangle2D autoLabelBounds = autoLabelShape.getBounds2D();
AffineTransform autoAffTrans = new AffineTransform();
autoAffTrans.translate(
(xChromoXStartInPixels / 2.0) - (autoLabelBounds.getWidth() / 2.0),
autoLodYInPixels - 1.0);
autoLabelShape = autoAffTrans.createTransformedShape(autoLabelShape);
graphics2D.fill(autoLabelShape);
}
}
else
{
double lodYInPixels = coordConverter.convertGraphYCoordinateToJava2DYCoordinate(
threshold.getLodValue());
Line2D alphaThresholdLine = new Line2D.Double(
xAllChromosStartInPixels, lodYInPixels,
xAllChromosStopInPixels, lodYInPixels);
graphics2D.draw(ScanOneGraph.ALPHA_THRESHOLD_STROKE.createStrokedShape(
alphaThresholdLine));
if(this.getShowThresholdLabel())
{
String labelString =
"\u03B1 Threshold : " +
Constants.THREE_DIGIT_FORMATTER.format(threshold.getAlphaThreshold());
Shape labelShape = this.createGraphTextShape(
graphics2D,
labelString);
Rectangle2D labelBounds = labelShape.getBounds2D();
AffineTransform at = new AffineTransform();
at.translate(
((xAllChromosStopInPixels + xAllChromosStartInPixels) / 2.0) -
(labelBounds.getWidth() / 2.0),
lodYInPixels - 1.0);
labelShape = at.createTransformedShape(labelShape);
graphics2D.fill(labelShape);
}
}
}
/**
* Turn the given string into a shape object
* @param graphics
* the graphics context that the shape will live in
* @param text
* the text to create a shape for
* @return
* the shape of the text given the graphics context
*/
private Shape createGraphTextShape(
Graphics2D graphics,
String text)
{
FontRenderContext frc = graphics.getFontRenderContext();
GlyphVector labelGlyphVector = GRAPH_TEXT_FONT.createGlyphVector(
frc,
text);
Shape textShape = labelGlyphVector.getOutline();
return textShape;
}
/**
* Draw a line highlighting the given marker
* @param graphics2D
* the graphics context
* @param markerToHighlight
* the marker to highlight
*/
private void drawMarkerHighlight(
Graphics2D graphics2D,
GeneticMarker markerToHighlight)
{
double markerPositionX = this.markerPositionManager.getMarkerPositionInGraphUnits(
markerToHighlight);
double markerPositionXInPixels = this.getGraphCoordinateConverter().convertGraphXCoordinateToJava2DXCoordinate(
markerPositionX);
double highlightYStartInPixels =
this.getGraphCoordinateConverter().getAbsoluteYOffsetInPixels();
double highlightYStopInPixels =
this.getGraphCoordinateConverter().getAbsoluteHeightInPixels() +
highlightYStartInPixels;
Line2D highlightLine = new Line2D.Double(
markerPositionXInPixels, highlightYStartInPixels,
markerPositionXInPixels, highlightYStopInPixels);
// TODO color should not be hard coded
Color pushColor = graphics2D.getColor();
graphics2D.setColor(Color.RED);
graphics2D.draw(highlightLine);
graphics2D.setColor(pushColor);
}
/**
* Render a chromosome separator
* @param graphics2D
* the graphics context to render the separator to
* @param separatorPosition
* where we should put the separator
*/
private void drawChromosomeSeparator(
Graphics2D graphics2D,
double separatorPosition)
{
double separatorXInPixels =
this.getGraphCoordinateConverter().convertGraphXCoordinateToJava2DXCoordinate(
separatorPosition);
double separatorYStartInPixels =
this.getGraphCoordinateConverter().getAbsoluteYOffsetInPixels();
double separatorYStopInPixels =
this.getGraphCoordinateConverter().getAbsoluteHeightInPixels() +
separatorYStartInPixels;
Line2D separatorLine = new Line2D.Double(
separatorXInPixels, separatorYStartInPixels,
separatorXInPixels, separatorYStopInPixels);
// TODO color should not be hard coded
Color pushColor = graphics2D.getColor();
graphics2D.setColor(Color.LIGHT_GRAY);
graphics2D.draw(ScanOneGraph.CHROMOSOME_SEPARATOR_STROKE.createStrokedShape(
separatorLine));
graphics2D.setColor(pushColor);
}
/**
* Draw a line connecting the given marker values
* @param graphics2D
* the graphics context to render to
* @param marker1
* the 1st marker
* @param marker1Value
* the 1st marker's value
* @param marker2
* the 2nd marker
* @param marker2Value
* the 2nd marker's value
*/
private void connectMarkerValuesPairs(
Graphics2D graphics2D,
GeneticMarker marker1,
double marker1Value,
GeneticMarker marker2,
double marker2Value)
{
// figure our the location of each marker on the x axis
double marker1PosX =
this.markerPositionManager.getMarkerPositionInGraphUnits(
marker1);
double marker2PosX =
this.markerPositionManager.getMarkerPositionInGraphUnits(
marker2);
// do a conversion from graph space to pixel space
double pixleX1 =
this.getGraphCoordinateConverter().convertGraphXCoordinateToJava2DXCoordinate(
marker1PosX);
double pixleY1 =
this.getGraphCoordinateConverter().convertGraphYCoordinateToJava2DYCoordinate(
marker1Value);
double pixleX2 =
this.getGraphCoordinateConverter().convertGraphXCoordinateToJava2DXCoordinate(
marker2PosX);
double pixleY2 =
this.getGraphCoordinateConverter().convertGraphYCoordinateToJava2DYCoordinate(
marker2Value);
// render the connection
graphics2D.draw(new Line2D.Double(
pixleX1,
pixleY1,
pixleX2,
pixleY2));
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void setContainerComponent(
JComponent containerComponent)
{
// stop listening to old component
JComponent currContainerComponent = this.getContainerComponent();
if(currContainerComponent != null)
{
currContainerComponent.removeMouseListener(
this.containerComponentMouseListener);
currContainerComponent.removeMouseMotionListener(
this.containerComponentMotionListener);
currContainerComponent.remove(this.toolTip);
}
super.setContainerComponent(containerComponent);
// start listening to new component
if(containerComponent != null)
{
containerComponent.addMouseListener(
this.containerComponentMouseListener);
containerComponent.addMouseMotionListener(
this.containerComponentMotionListener);
}
}
/**
* {@inheritDoc}
*/
public AxisDescription getXAxisDescription()
{
return this.xAxisDescription;
}
/**
* {@inheritDoc}
*/
public AxisDescription getYAxisDescription()
{
return this.yAxisDescription;
}
/**
* Setter for the thresholds that this graph should render
* @param thresholdsToRender the thresholdsToRender to set
*/
public void setThresholdsToRender(ScanOneThreshold[] thresholdsToRender)
{
this.thresholdsToRender = thresholdsToRender;
this.getContainerComponent().repaint();
}
/**
* Getter for the thresholds to render
* @return the thresholdsToRender
*/
public ScanOneThreshold[] getThresholdsToRender()
{
return this.thresholdsToRender;
}
}