/* * 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.graph; import java.awt.Color; import java.awt.Dimension; import java.awt.Shape; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import javax.swing.ToolTipManager; import org.jax.qtl.cross.Cross; import org.jax.qtl.cross.CrossChromosome; import org.jax.qtl.cross.GeneticMarker; import org.jax.qtl.util.Tools; /** * <p>Title: QTL data analysis</p> * * <p>Description: </p> * * <p>Company: The Jackson Laboratory</p> * * @author Lei Wu * @version 1.0 */ @SuppressWarnings("all") public class RfPlot extends OneDimensionPlot { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = -3979909287609271087L; private Cross cross; private double[][] rfLod; private Color[] colorMap; private double minlodLower = 0, maxlodLower = MAX_RF_LOD, minlodUpper = 0, maxlodUpper = MAX_RF_LOD; private int leftConerX, leftConerY, width, height; private CrossChromosome[] selectedChromosomes; private RfGrid[][] dataPoints; private List<GeneticMarker> allSelectedMarkers; public RfPlot(Cross cross, double[][] allRFLod, int[] selectedChromosomeIndexes) { super(); this.drawOutlineBox = false; List<CrossChromosome> allChromosomes = cross.getGenotypeData(); int numSelectedChromosomes = selectedChromosomeIndexes.length; this.allSelectedMarkers = new ArrayList<GeneticMarker>(); this.selectedChromosomes = new CrossChromosome[numSelectedChromosomes]; int allChromosomesCount = allChromosomes.size(); List<List<GeneticMarker>> allChromosomeMarkers = new ArrayList<List<GeneticMarker>>(); int[] cumulativeChromosomeMarkerCounts = new int[allChromosomesCount]; for(int i = 0; i < allChromosomesCount; i++) { List<GeneticMarker> markerPositions = allChromosomes.get(i).getAnyGeneticMap().getMarkerPositions(); allChromosomeMarkers.add( markerPositions); cumulativeChromosomeMarkerCounts[i] = markerPositions.size(); if(i > 0) { cumulativeChromosomeMarkerCounts[i] += cumulativeChromosomeMarkerCounts[i - 1]; } } for(int selectionIndex = 0; selectionIndex < numSelectedChromosomes; selectionIndex++) { CrossChromosome currSelectedChromosome = allChromosomes.get(selectedChromosomeIndexes[selectionIndex]); this.selectedChromosomes[selectionIndex] = currSelectedChromosome; this.allSelectedMarkers.addAll( allChromosomeMarkers.get( selectedChromosomeIndexes[selectionIndex])); } int selectedMarkerCount = this.allSelectedMarkers.size(); this.rfLod = new double[selectedMarkerCount][selectedMarkerCount]; { int outerRfDataIndex = 0; for(int outerChromoIndex = 0; outerChromoIndex < numSelectedChromosomes; outerChromoIndex++) { int outerStartingMarkerIndexInclusive = selectedChromosomeIndexes[outerChromoIndex] == 0 ? 0 : cumulativeChromosomeMarkerCounts[selectedChromosomeIndexes[outerChromoIndex] - 1]; int outerEndingMarkerIndexExclusive = cumulativeChromosomeMarkerCounts[selectedChromosomeIndexes[outerChromoIndex]]; for(int outerMarkerIndex = outerStartingMarkerIndexInclusive; outerMarkerIndex < outerEndingMarkerIndexExclusive; outerMarkerIndex++) { int innerRfDataIndex = 0; for(int innerChromoIndex = 0; innerChromoIndex < numSelectedChromosomes; innerChromoIndex++) { int innerStartingMarkerIndexInclusive = selectedChromosomeIndexes[innerChromoIndex] == 0 ? 0 : cumulativeChromosomeMarkerCounts[selectedChromosomeIndexes[innerChromoIndex] - 1]; int innerEndingMarkerIndexExclusive = cumulativeChromosomeMarkerCounts[selectedChromosomeIndexes[innerChromoIndex]]; for(int innerMarkerIndex = innerStartingMarkerIndexInclusive; innerMarkerIndex < innerEndingMarkerIndexExclusive; innerMarkerIndex++) { this.rfLod[outerRfDataIndex][innerRfDataIndex] = allRFLod[outerMarkerIndex][innerMarkerIndex]; innerRfDataIndex++; } } outerRfDataIndex++; } } } // TODO add back the interaction // int numChr = chrs.length; // for (int i=0; i<numChr; i++) { // int numMarkers = chrs[i].getNumMarkers(); // for (int j=0; j<numMarkers; j++) { // GeneticMap map = ((MarkerData)chrs[i].getMarkers().elementAt(j)).getMap(); // map.addGenoDataSelectionChangeListener(this); // markers.add(map); // } // } // make the dictionary for showing tip this.dataPoints = new RfGrid[selectedMarkerCount][selectedMarkerCount]; for (int row=0; row<selectedMarkerCount; row++) { for (int col=0; col<selectedMarkerCount; col++) { String markerName1 = this.allSelectedMarkers.get(row).getMarkerName(); String markerName2 = this.allSelectedMarkers.get(col).getMarkerName(); // set the correct value for displayed in tip double rf = 0, lod = 0; if (row == col) rf = lod = this.rfLod[row][col]; else { if (row > col) { // lower in data, upper in plot rf = this.rfLod[row][col]; lod = this.rfLod[col][row]; } else { // upper in data, lower in plot rf = this.rfLod[col][row]; lod = this.rfLod[row][col]; } } if (rf > 0.5) rf = 0.5; this.dataPoints[row][col] = new RfGrid(markerName1, markerName2, rf, lod); } } // make color map this.colorMap = Tools.makeColormap(NUM_COLORS); // set selection box "color" this.selectionBoxColor = new Color(0,0,0,30); // set title and labels setTitle("Pairwise recombination fractions and LOD scores "); int preferedWidth = 500, preferedHeight = 500; setPreferredSize(new Dimension(preferedWidth, preferedHeight)); } /** * plot the rf plot */ void plot() { this.allDots = new HashSet<Dot>(); this.leftConerX = this.inset.left; this.leftConerY = this.inset.top; // total number of rows, columns (rows=columns=numMarkers) int numMarkers = this.allSelectedMarkers.size(); int gridWidth = this.plotWidth/numMarkers; int gridHeight = this.plotHeight/numMarkers; this.width = gridWidth * numMarkers; this.height = gridHeight * numMarkers; // draw grids for (int row=0; row<numMarkers; row++) { for (int col=0; col<numMarkers; col++) { int x = gridWidth * col + this.leftConerX; int y = this.leftConerY + this.height - gridHeight * (row+1); Shape currentGrid = new Rectangle2D.Double(x,y,this.width/numMarkers,this.height/numMarkers); // set the correct value for displayed in tip Dot dot = new Dot( this.allSelectedMarkers.get(row), this.allSelectedMarkers.get(col), currentGrid); this.allDots.add(dot); // set color for current grid boolean isLower = true; if (row==col) // on diagnal this.big.setColor(Color.red); else { double plotValue = this.rfLod[row][col]; if (row > col) { // (rf) lower in data, upper in plot isLower = false; plotValue = this.rfLod[row][col]; plotValue = (-4) * (Math.log(plotValue)/Math.log(2) + 1); if (plotValue < 0) plotValue = 0; } if (plotValue > MAX_RF_LOD) // (lod) upper in data, lower in plot plotValue = MAX_RF_LOD; this.big.setColor(getColor(plotValue, isLower)); } this.big.fill(currentGrid); // highlight the selected grid // TODO add back the interaction // if (((GeneticMap)markers.elementAt(row)).isSelected() && ((GeneticMap)markers.elementAt(col)).isSelected() && (row!=col)) { // big.setColor(Color.white); // big.setStroke(new BasicStroke(1.8f)); // big.draw(currentGrid); // } } } // draw chromosome dividers, (numChr - 1) crossed lines int numChr = this.selectedChromosomes.length; int cumulatedNumMarkers = 0; int lastx = this.leftConerX, lasty = this.leftConerY + this.height; for (int i=0; i<numChr; i++) { int numMarkersOnOneChr = this.selectedChromosomes[i].getAnyGeneticMap().getMarkerPositions().size(); cumulatedNumMarkers += numMarkersOnOneChr; int x = cumulatedNumMarkers * gridWidth + this.leftConerX; int y = this.leftConerY + this.height - cumulatedNumMarkers * gridHeight; int tickx = lastx + (numMarkersOnOneChr * gridWidth)/2; int ticky = lasty - (numMarkersOnOneChr * gridHeight)/2; lastx = x; lasty = y; this.big.setColor(Color.white); this.big.setStroke(this.normalLinetype); // draw vertical lines this.big.drawLine(x, this.leftConerY, x, this.leftConerY + this.height); // draw horizontal lines this.big.drawLine(this.leftConerX, y, this.leftConerX + this.width, y); // draw ticks this.big.setColor(this.normalColor); this.big.drawLine(tickx, this.leftConerY + this.height, tickx, this.leftConerY + this.height + this.tickHeight); // x axis ticks this.big.drawLine(this.leftConerX - this.tickHeight, ticky, this.leftConerX, ticky); // y axis ticks // label width and height Rectangle2D labelBounds = this.tickLabelFont.getStringBounds( this.selectedChromosomes[i].getChromosomeName(), this.context); double labelWidth = labelBounds.getWidth(); double labelHeight = labelBounds.getHeight(); // find the right place to start to draw labels on y axis float yAxisLabelStartX = this.leftConerX - (float) labelWidth - this.tickHeight - this.labelToTick; float yAxisLabelStartY = (float) (ticky + labelHeight / 2 - 1); // -1 is only for looks prettier // draw tick label on y axis this.big.setFont(this.tickLabelFont); this.big.drawString( this.selectedChromosomes[i].getChromosomeName(), yAxisLabelStartX, yAxisLabelStartY); // vertical // find the right place to start to draw labels on x axis float xAxisLabelStartX = tickx - (float)labelWidth/2 + 0.5f; // 0.5f is only for looks prettier float xAxisLabelStartY = this.leftConerY + this.height + this.tickHeight + (float)labelHeight + this.labelToTick; // draw tick label on x axis this.big.drawString( this.selectedChromosomes[i].getChromosomeName(), xAxisLabelStartX, xAxisLabelStartY); // horizontal } // draw outlines around the plot this.big.drawRect(this.leftConerX, this.leftConerY, this.width, this.height); } public void setSize(Dimension size) { // this.setSize(new Dimension(500, 500)); } // for MouseMotionListener public void mouseMoved(MouseEvent e) { int x = e.getX(); int y = e.getY(); if ((x > this.leftConerX && x < this.leftConerX + this.width) && (y > this.leftConerY && y < this.leftConerY + this.height)) { // find the current grid int numMarkers = this.allSelectedMarkers.size(); int deltaX = x - this.leftConerX; // distance to left of plot int deltaY = this.leftConerY + this.height - y; // distance to top of plot int r = numMarkers * deltaX / this.width; // row index int c = numMarkers * deltaY / this.height; // col index RfGrid currentGrid = this.dataPoints[r][c]; // make tip String tip = "<html>"; if (currentGrid.markerName1.equalsIgnoreCase(currentGrid.markerName2)) // on diagnal tip += currentGrid.markerName1 + "<p>typed meioses: " + currentGrid.lod; else tip += currentGrid.markerName1 + ":" + currentGrid.markerName2 + "<p>rf: " + FOUR_DIGIT_FORMATTER.format(currentGrid.rf) + "<p>LOD: " + FOUR_DIGIT_FORMATTER.format(currentGrid.lod); // show tip within plot area ToolTipManager.sharedInstance().setEnabled(true); this.setToolTipText(tip); setCursor(this.plotCursor); return; } else { ToolTipManager.sharedInstance().setEnabled(false); setCursor(this.normalCursor); } } private Color getColor(double value, boolean isLower) { int index = 0; if (isLower) index = (int) ( (value - this.minlodLower) / ( (this.maxlodLower - this.minlodLower) / NUM_COLORS)); else index = (int) ( (value - this.minlodUpper) / ( (this.maxlodUpper - this.minlodUpper) / NUM_COLORS)); if (index < 0) index = 0; if (index > 255) index = 255; return this.colorMap[index]; } class RfGrid { double rf, lod; String markerName1, markerName2; RfGrid(String mname1, String mname2, double rf, double lod) { this.markerName1 = mname1; this.markerName2 = mname2; this.rf = rf; this.lod = lod; } } }