/* * 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.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.Hashtable; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JEditorPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ToolTipManager; import org.jax.analyticgraph.data.NamedCategoricalData; import org.jax.analyticgraph.data.NamedData; import org.jax.analyticgraph.data.NamedDataMatrix; import org.jax.analyticgraph.data.NamedRealData; import org.jax.qtl.QTL; import org.jax.qtl.cross.Cross; import org.jax.qtl.cross.CrossChromosome; import org.jax.qtl.cross.GeneticMap; import org.jax.qtl.cross.GeneticMarker; import org.jax.util.math.Matlab; /** * Panel for plotting genotype data * @author Hao Wu * @author Keith Sheppard (minor modifications for integrating w/ J/qtl 1.0) */ public class GenoPlot extends JPanel implements MouseMotionListener { /** * */ private static final long serialVersionUID = 6986895085781481197L; /** * our logger */ private static final Logger LOG = Logger.getLogger( GenoPlot.class.getName()); @SuppressWarnings("unchecked") private Hashtable GenoPlotProperties; // figure properties private BufferedImage bImage; private Graphics2D g2; private static final int LEFT=50, TOP=70, BOTTOM=20, RIGHT=20; private int WIDTH, HEIGHT; // width and height of the figure private int NCOL=0, NROW=0; // number of rows and columns in geno data to be plot private int plotLeft=LEFT, plotTop=TOP, plotRight, plotBottom; private int currentx=0, currenty=0; private int thisChrIdx=0, thisIndIdx=0, thisMarIdx=0; private boolean inPlotRegion; private int SPACING; // space between chromosomes private int YSPACING=3; // space between individuals for plotting in real marker distance private String title = ""; private final Cross cross; private int nchr; // total number of chromosomes private double[] chrlen; // chromosome length private int nind; // total number of individuals private int[] nmar; // number of markers on each chromosome // adjusted marker position for all chromosomes // this is only used when plotting in real marker distance private double[][] mpos=null; private double[][] xregion; private int[] sortIdx; // mouse listener private MyMouseListener mouselistener; // ======== figure properties ============ // colors private Color[] GenoColor; // color for genotypes private Color MissingColor; // color for missing genotype // block size private int XSPACE, YSPACE; // sort by what private int sortbyidx; // what to plot: 0 - genotype, 1 - crossoves, 2 - missing, 3-errorlod private int whattoplot; // whether the plot should be interactive private boolean interactive; // whether to plot in the real marker distance or not private boolean inMarkDist; // chr and ind index private int[] chridx, indidx; // for plotting error lod private double[] errorlod_breaks; private Color[] errorlod_colors; /** * Constructor * @param c the cross * @param properties the properties */ @SuppressWarnings("unchecked") public GenoPlot(Cross c, Hashtable properties) { this.GenoPlotProperties = properties; this.cross = c; // total number of chromosomes and inds this.nchr = this.cross.getNumberOfChromosomes(); this.nind = this.cross.getNumberOfIndividuals(); this.nmar = this.cross.getNumberOfMarkers(); // chromosome length this.chrlen = GenoPlot.getAllChromosomeLengths(this.cross); // get figure properties getFigureProperties(); // sort the individuals sort(this.sortbyidx); // prepare the figure calcPlotRegion(); setBackground(Color.white); setPreferredSize(new Dimension(this.WIDTH, this.HEIGHT)); createBufferedImage(); // add mouse listeners if it's interactive plot this.mouselistener = new MyMouseListener(); if(this.interactive) { addMouseListener(this.mouselistener); addMouseMotionListener(this); } } private static double[] getAllChromosomeLengths(Cross cross) { List<CrossChromosome> genotypeData = cross.getGenotypeData(); double[] allChromosomeLengths = new double[genotypeData.size()]; for(int i = 0; i < allChromosomeLengths.length; i++) { // TODO need to handle sex specific here too allChromosomeLengths[i] = GeneticMap.getTotalExtentOfMarkerListInCentimorgans( genotypeData.get(i).getAnyGeneticMap().getMarkerPositions()); } return allChromosomeLengths; } /** * {@inheritDoc} */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.drawImage(this.bImage, 0, 0, this.WIDTH, this.HEIGHT, this); } /** * sort image by a given pheno type * @param pheidx the index */ public void sort(int pheidx) { if(pheidx == 0) { this.sortIdx = new int[this.nind]; for(int i=0; i<this.nind; i++) this.sortIdx[i] = i; } else{ // get the phenotype List<Number> phenoList = this.cross.getPhenotypeData().getNamedDataList().get(pheidx-1).getData(); double[] pheno = new double[phenoList.size()]; for(int i = 0; i < pheno.length; i++) { Number currPhenoNumber = phenoList.get(i); if(currPhenoNumber == null) { pheno[i] = Double.NEGATIVE_INFINITY; } else { pheno[i] = currPhenoNumber.doubleValue(); } } this.sortIdx = Matlab.order(pheno); if(LOG.isLoggable(Level.FINE)) { LOG.fine( "Sorted individuals by phenotype: " + this.cross.getPhenotypeData().getDataNames()[pheidx - 1]); StringBuffer newOrderBuffer = new StringBuffer("New order is:"); StringBuffer sortedPhenoBuffer = new StringBuffer( "Phenotypes in sorted order are:"); for(int currIndex: this.sortIdx) { newOrderBuffer.append(" "); newOrderBuffer.append(currIndex); sortedPhenoBuffer.append(" "); sortedPhenoBuffer.append(pheno[currIndex]); } LOG.fine(newOrderBuffer.toString()); LOG.fine(sortedPhenoBuffer.toString()); } } } // get the figure properties from hash table private void getFigureProperties() { this.sortbyidx = ((Integer)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_SORTBY)).intValue(); this.XSPACE = ((Integer)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_XSIZE)).intValue(); this.YSPACE = ((Integer)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_YSIZE)).intValue(); this.SPACING = (this.XSPACE+1) * 2; // calculate spacing based on xspace this.GenoColor = (Color[])this.GenoPlotProperties.get(FigureProperties.GENOPLOT_PALETTE); this.MissingColor = this.GenoColor[this.GenoColor.length-1]; this.whattoplot = ((Integer)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_WHAT)).intValue(); this.interactive = ((Boolean)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_INT)).booleanValue(); // if(whattoplot != 0) // interactive = false; this.inMarkDist = ((Boolean)this.GenoPlotProperties.get(FigureProperties.GENOPLOT_IN_MARKER_DIST)) .booleanValue(); // for errorlod this.errorlod_breaks = (double [])this.GenoPlotProperties.get(FigureProperties.GENOPLOT_ERRORLOD_BREAKS); this.errorlod_colors = (Color[])this.GenoPlotProperties.get(FigureProperties.GENOPLOT_ERRORLOD_COLORS); // chrmosome index Object otmp; otmp = this.GenoPlotProperties.get(FigureProperties.GENOPLOT_CHROM); if(otmp == "all") { // all chromosomes this.chridx = new int[this.nchr]; for(int i=0; i<this.nchr; i++) this.chridx[i] = i; } else { //chose some chromosome. The property entry should be a int[] this.chridx = (int[])this.GenoPlotProperties.get(FigureProperties.GENOPLOT_CHROM); } // individual index otmp = this.GenoPlotProperties.get(FigureProperties.GENOPLOT_IND); if(otmp.equals("all")) { // all individuals this.indidx = new int[this.nind]; for(int i=0; i<this.nind; i++) this.indidx[i] = i; } else {// I will do this later this.indidx = (int[])otmp; } // some other related parameters this.xregion = new double[this.chridx.length][2]; this.NROW = this.indidx.length; this.NCOL = 0; for(int i=0; i<this.chridx.length; i++) this.NCOL = this.NCOL + this.nmar[this.chridx[i]]; // NCOL = cross.getNtotmar(); // marker positions if(this.inMarkDist) { this.mpos = new double[this.chridx.length][]; for(int i = 0; i < this.chridx.length; i++) { List<GeneticMarker> currMarkerPositions = this.cross.getGenotypeData().get(i).getAnyGeneticMap().getMarkerPositions(); this.mpos[i] = new double[currMarkerPositions.size()]; // use the starting position to adjust all other markers double mpos0 = currMarkerPositions.get(0).getMarkerPositionCentimorgans(); for(int j = 0; j < this.nmar[this.chridx[i]]; j++) { this.mpos[i][j] = currMarkerPositions.get(j).getMarkerPositionCentimorgans() - mpos0; } } } } // create buffered image private void createBufferedImage() { // Create buffered image that does not support transparency this.bImage = new BufferedImage(this.WIDTH, this.HEIGHT, BufferedImage.TYPE_3BYTE_BGR); this.g2 = this.bImage.createGraphics(); // fill background with white // g2.setColor(Color.GRAY); this.g2.fillRect(0, 0, this.WIDTH, this.HEIGHT); doGenoPlot(); this.g2.dispose(); } // generate genotype plot private void doGenoPlot() { int x=0, y=0, xsp, ysp; // double data; // genotype data or errorlod // re-calculate plot region // calcPlotRegion(); int colidx = 0; int xoffset = GenoPlot.LEFT + 1; // the calculation of xregion is a little off // I don't want to waste time to correct it 'cause it's not that important. // the following code works fine for all cases. if(this.inMarkDist) this.xregion[0][0] = xoffset; else this.xregion[0][0] = xoffset - 1; // grab genocolor from figure property this.GenoColor = (Color[])this.GenoPlotProperties.get(FigureProperties.GENOPLOT_PALETTE); this.MissingColor = this.GenoColor[this.GenoColor.length-1]; // the xspace and yspace value depends on whether there's seperation lines // or not. if(this.interactive) { xsp = this.XSPACE + 1; ysp = this.YSPACE + 1; } else { xsp = this.XSPACE; ysp = this.YSPACE; } // plot some solid lines if plotting in real marker distance if(this.inMarkDist) { // Stroke solid_line = new BasicStroke(2f); // g2.setStroke(solid_line); this.g2.setColor(Color.black); x = GenoPlot.LEFT+1; for(int i=0; i<this.chridx.length; i++) { y = GenoPlot.TOP+ysp/2; int thischrlen = (int)this.chrlen[this.chridx[i]]+this.nmar[this.chridx[i]]*xsp-1; for(int j=0; j<this.indidx.length; j++) { this.g2.drawLine(x, y, x+thischrlen-1, y); y = y + (this.YSPACING+ysp); } x = x + thischrlen + this.SPACING; } } // plotting genotypes/missing/crossover/errorlod int[] markerCounts = this.cross.getNumberOfMarkers(); if(this.whattoplot == 0) { // loop thru chromosomes for(int i=0; i<this.chridx.length; i++) { int thischr = this.chridx[i]; CrossChromosome currChromo = this.cross.getGenotypeData().get(thischr); List<NamedCategoricalData> currGenotypes = currChromo.getMarkerGenotypes(); // loop thru markers for(int j=0; j<markerCounts[thischr]; j++) { if(this.inMarkDist) { x = xoffset + colidx*xsp + (int)this.mpos[i][j]; } else x = colidx*xsp + xoffset; // loop thru individuals for(int k=0; k<this.indidx.length; k++) { int thisind = this.indidx[k]; if(this.inMarkDist) y = GenoPlot.TOP+1+(ysp+this.YSPACING)*k; else y = k*ysp + 1 + GenoPlot.TOP; Number data = currGenotypes.get(j).getData().get(this.sortIdx[thisind]); if(data == null) this.g2.setColor(this.MissingColor); else this.g2.setColor(this.GenoColor[data.intValue()]); this.g2.fillRect(x, y, this.XSPACE, this.YSPACE); } colidx ++; } xoffset = xoffset + this.SPACING; if(this.inMarkDist) xoffset = xoffset + (int)this.chrlen[thischr] - 1; this.xregion[i][1] = x + this.XSPACE; if(i != this.chridx.length-1) this.xregion[i+1][0] = x + this.XSPACE + this.SPACING; } } else if(this.whattoplot == 1) { // for plotting crossovers if(this.inMarkDist) { // in real marker distance // plot "/" for single crossover and "X" for double crossover // loop thru chromosomes for(int i=0; i<this.chridx.length; i++) { int thischr = this.chridx[i]; colidx ++; // loop thru markers CrossChromosome currChromo = this.cross.getGenotypeData().get(thischr); List<NamedCategoricalData> currGenotypes = currChromo.getMarkerGenotypes(); for(int j=1; j<markerCounts[thischr]; j++) { x = xoffset + colidx*xsp + (int)this.mpos[i][j]; // loop thru individuals for(int k=0; k<this.indidx.length; k++) { int thisind = this.indidx[k]; Number previous = currGenotypes.get(j - 1).getData().get(this.sortIdx[thisind]); Number current = currGenotypes.get(j).getData().get(this.sortIdx[thisind]); if(current != null && previous != null && !current.equals(previous)) { this.g2.setColor(Color.BLACK); y = GenoPlot.TOP+1+(ysp+this.YSPACING)*k; if(Math.abs(previous.intValue()-current.intValue()) == 1) //single crossover { drawSlash(x,y); } else // double crossover { drawCross(x,y); } } } colidx ++; } xoffset = xoffset + this.SPACING + (int)this.chrlen[thischr] - 1; this.xregion[i][1] = x + this.XSPACE; if(i != this.chridx.length-1) this.xregion[i+1][0] = x + this.XSPACE + this.SPACING; } } else{ // if not in real marker distance // loop thru chromosomes for(int i=0; i<this.chridx.length; i++) { int thischr = this.chridx[i]; // loop thru markers colidx ++; CrossChromosome currChromo = this.cross.getGenotypeData().get(thischr); List<NamedCategoricalData> currGenotypes = currChromo.getMarkerGenotypes(); for(int j=1; j<markerCounts[thischr]; j++) { x = colidx*xsp + xoffset; // loop thru individuals for(int k=0; k<this.indidx.length; k++) { int thisind = this.indidx[k]; Number previous = currGenotypes.get(j - 1).getData().get(this.sortIdx[thisind]); Number current = currGenotypes.get(j).getData().get(this.sortIdx[thisind]); if(current != null && previous != null && !current.equals(previous)) { y = k*ysp + 1 + TOP; // plot previous and current this.g2.setColor(this.GenoColor[previous.intValue()]); this.g2.fillRect(x-xsp, y, this.XSPACE, this.YSPACE); this.g2.setColor(this.GenoColor[current.intValue()]); this.g2.fillRect(x, y, this.XSPACE, this.YSPACE); } } colidx ++; } xoffset = xoffset + this.SPACING; this.xregion[i][1] = x + this.XSPACE; if(i != this.chridx.length-1) this.xregion[i+1][0] = x + this.XSPACE + this.SPACING; // plot a box for this chromosome if it's not in real marker distance this.g2.setColor(Color.lightGray); this.g2.drawRect((int)this.xregion[i][0], TOP, (int)(this.xregion[i][1]-this.xregion[i][0]), ysp*this.nind); } } } else if(this.whattoplot == 2) { // plot missing values only // loop thru chromosomes for(int i=0; i<this.chridx.length; i++) { int thischr = this.chridx[i]; CrossChromosome currChromo = this.cross.getGenotypeData().get(thischr); List<NamedCategoricalData> currGenotypes = currChromo.getMarkerGenotypes(); // loop thru markers for(int j=0; j<markerCounts[thischr]; j++) { if(this.inMarkDist) { x = xoffset + colidx*xsp + (int)this.mpos[i][j]; } else x = colidx*xsp + xoffset; // loop thru individuals for(int k=0; k<this.indidx.length; k++) { int thisind = this.indidx[k]; Number data = currGenotypes.get(j).getData().get(this.sortIdx[thisind]); if(data == null) { this.g2.setColor(this.MissingColor); if(this.inMarkDist) y = TOP+1+(ysp+this.YSPACING)*k; else y = k*ysp + 1 + TOP; this.g2.fillRect(x, y, this.XSPACE, this.YSPACE); } } colidx ++; } xoffset = xoffset + this.SPACING; if(this.inMarkDist) xoffset = xoffset + (int)this.chrlen[thischr] - 1; this.xregion[i][1] = x + this.XSPACE; if(i != this.chridx.length-1) this.xregion[i+1][0] = x + this.XSPACE + this.SPACING; // plot a box for this chromosome if it's not in real marker distance if(!this.inMarkDist) { this.g2.setColor(Color.lightGray); this.g2.drawRect((int)this.xregion[i][0], TOP, (int)(this.xregion[i][1]-this.xregion[i][0]), ysp*this.nind); } } } else if(this.whattoplot == 3) { // plot error lod Color col; // calculate the error lods if they haven't been calculated yet. if(!this.cross.getErrorLodsExist()) { this.cross.calculateErrorLods(); } // loop thru chromosomes for(int i=0; i<this.chridx.length; i++) { int thischr = this.chridx[i]; CrossChromosome currChromo = this.cross.getGenotypeData().get(thischr); List<NamedRealData> currErrorLods = currChromo.getMarkerErrorLods(); // loop thru markers for(int j=0; j<markerCounts[thischr]; j++) { if(this.inMarkDist) { x = xoffset + colidx*xsp + (int)this.mpos[i][j]; } else x = colidx*xsp + xoffset; // loop thru individuals for(int k=0; k<this.indidx.length; k++) { double data = currErrorLods.get(j).getRealNumericalData()[k].doubleValue(); // find the color col = this.errorlod_colors[0]; for(int ii=this.errorlod_breaks.length-2; ii>0; ii--) { if(data > this.errorlod_breaks[ii]) { col = this.errorlod_colors[ii]; break; } } this.g2.setColor(col); if(this.inMarkDist) y = TOP+1+(ysp+this.YSPACING)*k; else y = k*ysp + 1 + TOP; this.g2.fillRect(x, y, this.XSPACE, this.YSPACE); } colidx ++; } xoffset = xoffset + this.SPACING; if(this.inMarkDist) xoffset = xoffset + (int)this.chrlen[thischr] - 1; this.xregion[i][1] = x + this.XSPACE; if(i != this.chridx.length-1) this.xregion[i+1][0] = x + this.XSPACE + this.SPACING; // plot a box for this chromosome if it's not in real marker distance if(!this.inMarkDist) { this.g2.setColor(Color.lightGray); this.g2.drawRect((int)this.xregion[i][0], TOP, (int)(this.xregion[i][1]-this.xregion[i][0]), ysp*this.nind); } } } // draw title and chrmosome ID this.g2.setColor(Color.black); drawTitle(); drawChrID(); } // function to plot a slash and a cross - these are called by doGenoPlot // the size of the cross depends on XSPACE and YSPACE private void drawSlash(int x, int y) { this.g2.drawLine(x, y, x+this.XSPACE, y+this.YSPACE); } private void drawCross(int x, int y) { this.g2.drawLine(x, y, x+this.XSPACE, y+this.YSPACE); this.g2.drawLine(x, y+this.YSPACE, x+this.XSPACE, y); } // function to calculate the plot region private void calcPlotRegion() { if(this.inMarkDist) { // in real marker distance if(this.interactive) { this.WIDTH = this.NCOL*(this.XSPACE+1) + LEFT + RIGHT + this.SPACING*(this.chridx.length-1) + 1; this.HEIGHT = this.NROW*(this.YSPACE+this.YSPACING+1) + TOP + BOTTOM + 1; // plus the marker length for(int i=0; i<this.chridx.length; i++) { this.WIDTH = this.WIDTH + (int)this.chrlen[this.chridx[i]]; } } else { // non-interactive plot this.WIDTH = this.NCOL*this.XSPACE + LEFT + RIGHT + this.SPACING*(this.chridx.length-1) + 1; this.HEIGHT = this.NROW*(this.YSPACE+this.YSPACING) + TOP + BOTTOM + 1; // plus the marker length for(int i=0; i<this.chridx.length; i++) { this.WIDTH = this.WIDTH + (int)this.chrlen[this.chridx[i]]; } } } else { if(this.interactive) { this.WIDTH = this.NCOL*(this.XSPACE+1) + LEFT + RIGHT + this.SPACING*(this.chridx.length-1) + 1; this.HEIGHT = this.NROW*(this.YSPACE+1) + TOP + BOTTOM + 1; } else { this.WIDTH = this.NCOL*this.XSPACE + LEFT + RIGHT + this.SPACING*(this.chridx.length-1) + 1; this.HEIGHT = this.NROW*this.YSPACE + TOP + BOTTOM + 1; } } this.plotRight = this.WIDTH - RIGHT - 1; this.plotBottom = this.HEIGHT - BOTTOM - 1; } // /* zoom in and out functions */ // public void ZoomIn() { // if(this.XSPACE<=MAXELESIZE) { // this.XSPACE = this.XSPACE + 2; // this.YSPACE = this.YSPACE + 2; // this.SPACING = (this.XSPACE+1) * 2; // // save the setting // this.GenoPlotProperties.put(FigureProperties.GENOPLOT_XSIZE, new Integer(this.XSPACE)); // this.GenoPlotProperties.put(FigureProperties.GENOPLOT_YSIZE, new Integer(this.YSPACE)); // // redraw the figure // this.reDraw(); // } // } // @SuppressWarnings("unchecked") //private void ZoomOut() { // if(this.XSPACE>=MINELESIZE) { // this.XSPACE = this.XSPACE - 2; // this.YSPACE = this.YSPACE - 2; // this.SPACING = (this.XSPACE+1) * 2; // // save the setting // this.GenoPlotProperties.put(FigureProperties.GENOPLOT_XSIZE, new Integer(this.XSPACE)); // this.GenoPlotProperties.put(FigureProperties.GENOPLOT_YSIZE, new Integer(this.YSPACE)); // // redraw the figure // this.reDraw(); // } // } /* public void drawColorBox(Graphics g, Color color, int idx) { int xcoord = (col[idx]-1) * (XSPACE+1) + LEFT; int ycoord = (row[idx]-1) * (YSPACE+1) + TOP; g.setColor(color); g.drawRect(xcoord, ycoord, XSPACE+1, YSPACE+1); } */ /* public void drawColorBox(Graphics g, Color color, int x, int y) { boolean draw = true; int c, r, xcoord=0, ycoord=0; g.setColor(color); int xsp, ysp; if(interactive) { xsp = XSPACE + 1; ysp = YSPACE + 1; } else { xsp = XSPACE; ysp = YSPACE; } if(inMarkDist) { // in real marker distance // y-axis, this is easier r = (y-TOP) / (ysp+YSPACING); ycoord = r*(ysp+YSPACING) + TOP; // x-axis, this is troubler int xres = x; int x_chr=0, x_mar=0; // find which chromosome it's on for(int i=0; i<chridx.length; i++) { if((x>xregion[i][0]) && (x<xregion[i][1])) { x_chr = i; break; } } // find which marker it's on int start=LEFT, end=start+xsp; int thischrnmar = nmar[chridx[x_chr]]; for(int i=0; i<thischrnmar; i++) { if( (x>=start) && (x<=end) ) { xcoord = start; break; } if(i<thischrnmar-1) { start = end + (int)mpos[x_chr][i+1]; end = start + xsp; } } } else { // not on real marker distance. This is much easier c = (x-LEFT) / xsp; r = (y-TOP) / ysp; xcoord = c*xsp + LEFT; ycoord = r*ysp + TOP; } g.drawRect(xcoord, ycoord, xsp, ysp); } */ // show marker and individual name private void showTip() { // TODO add me back // int thischridx, markeridx, thisindidx; // thischridx = chridx[thisChrIdx]; // markeridx = thisMarIdx; // thisindidx = sortIdx[indidx[thisIndIdx]]; // CrossChromosome chr = cross.getGenotypeData().get(thischridx); // String mname = chr.get.getMarkerName(markeridx); // String msg=""; // // make the tips according to what to plot // // I will show genotypes if not plotting errorlod // if(whattoplot != 3) { // plot geno // // genotype // double geno = chr.getGenoData().get(thisindidx, markeridx); // String genoStr = ""; // if(Double.isNaN(geno)) // genoStr = "Missing"; // else { // String[] genocode = cross.getGenoCode(); // genoStr = "" + genocode[(int)geno-1]; // } // msg = "<html>Marker Name: " + mname + "<p>Individual Index: " + // (thisindidx+1) + "<p>Genotype: " + genoStr + "</html>"; // } // else if(whattoplot == 3) { // errorlod // DecimalFormat myFormatter = new DecimalFormat("#########0.000"); // double errorlod = chr.getErrorLodD().get(thisindidx, markeridx); // msg = "<html>Marker Name: " + mname + "<p>Individual Index: " + // (thisindidx+1) + "<p>Error LOD: " + // myFormatter.format(errorlod) + "</html>"; // } // setToolTipText(msg); } // plot highlight the selected marker private void highLightBox(int x, int y) { this.inPlotRegion = true; int start=0, end; //c, r, xcoord=0, ycoord=0, int xsp, ysp; Graphics g = getGraphics(); // box size if(this.interactive) { xsp = this.XSPACE + 1; ysp = this.YSPACE + 1; } else { xsp = this.XSPACE; ysp = this.YSPACE; } // see if it's in a valid plot region if(y<=this.plotTop) this.inPlotRegion = false; else if(y>=this.plotBottom) this.inPlotRegion = false; else if(x<=this.plotLeft) this.inPlotRegion = false; else if(x>=this.plotRight) this.inPlotRegion = false; // if not in real marker distance if(!this.inMarkDist) { // (x,y) must be within a chromosome if(this.inPlotRegion) { this.inPlotRegion = false; for(int i=0; i<this.chridx.length; i++) { if( (x>this.xregion[i][0]) && (x<this.xregion[i][1]) ) { this.thisChrIdx = i; this.inPlotRegion = true; break; } } // calculate the indices for this marker and this individual this.thisIndIdx = (y-TOP) / (ysp+this.YSPACING); this.thisMarIdx = (int)((x-this.xregion[this.thisChrIdx][0])/xsp); } } else { // in real marker distance if(this.inPlotRegion) { // if in real marker distance, must be within an individual int res; res = Matlab.mod(y-TOP, ysp+this.YSPACING); if(res>ysp) this.inPlotRegion = false; else this.thisIndIdx = ((y-TOP)/(ysp+this.YSPACING)); } if(this.inPlotRegion) { this.inPlotRegion = false; // this also must be on a marker // int x_chr=0, x_mar=0; // find which chromosome it's on for(int i=0; i<this.chridx.length; i++) { if((x>this.xregion[i][0]) && (x<this.xregion[i][1])) { this.thisChrIdx = i; break; } } // find which marker it's on // the calculation of xregion is a little off, I have to offset it here int start0 = (int)this.xregion[this.thisChrIdx][0] - 1; start=start0; end=start+xsp; int thischrnmar = this.nmar[this.chridx[this.thisChrIdx]]; for(int i=0; i<thischrnmar; i++) { if( (x>=start) && (x<=end) ) { this.thisMarIdx = i; this.inPlotRegion = true; break; } if(i<thischrnmar-1) { start = start0 + (i+1)*xsp + (int)this.mpos[this.thisChrIdx][i+1]; end = start + xsp; } } } } // overwrite the black box if( (this.currentx!=0) || (this.currenty!=0) ) { g.setColor(Color.white); g.drawRect(this.currentx, this.currenty, xsp, ysp); } if(!this.inPlotRegion) { // if it's not in a valid plot region //reset currentx and currenty this.currentx = 0; this.currenty = 0; } else { // it's in a valid plot region // calculate currentx and currenty if(this.inMarkDist) { this.currentx = start; this.currenty = this.thisIndIdx*(ysp+this.YSPACING) + TOP; } else { int c = (x-LEFT) / xsp; this.currentx = c*xsp + LEFT; // currentx = (int)xregion[thisChrIdx][0] + thisMarIdx*xsp; this.thisIndIdx = (y-TOP) / ysp; this.currenty = this.thisIndIdx*ysp + TOP; } // plot a black box and set currentx and currenty g.setColor(Color.black); g.drawRect(this.currentx, this.currenty, xsp, ysp); } } /* private boolean inPlotRegion(int x, int y) { if(y<plotTop) return false; else if(y>plotBottom) return false; else if(x<plotLeft) return false; else if(x>plotRight) return false; // (x,y) must be within a chromosome boolean flag = false; for(int i=0; i<chridx.length; i++) { if( (x>xregion[i][0]) && (x<xregion[i][1]) ) { flag = true; break; } } if(!flag) return false; // if in real marker distance, must be within an individual if(inMarkDist) { int res; int xsp, ysp; if(interactive) { xsp = XSPACE + 1; ysp = YSPACE + 1; } else { xsp = XSPACE; ysp = YSPACE; } res = Matlab.mod(y-TOP, ysp+YSPACING); if(res>ysp) return false; // this also must be on a marker int xres = x; int x_chr=0, x_mar=0; // find which chromosome it's on for(int i=0; i<chridx.length; i++) { if((x>xregion[i][0]) && (x<xregion[i][1])) { x_chr = i; break; } } // find which marker it's on int start=LEFT, end=start+xsp; int thischrnmar = nmar[chridx[x_chr]]; for(int i=0; i<thischrnmar; i++) { if( (x>=start) && (x<=end) ) { return false; } if(i<thischrnmar-1) { start = end + (int)mpos[x_chr][i+1]; end = start + xsp; } } } return true; } */ // draw chromosome IDs private void drawChrID() { Font f = new Font("SansSerif", Font.BOLD, 12); this.g2.setFont(f); FontMetrics fim = this.g2.getFontMetrics(f); int w; // String chrid; // for(int i=0; i<chridx.length; i++) { // chrid = cross.getChr(chridx[i]).getChrID(); // w = fim.stringWidth(chrid); // int cx = (int)((xregion[i][0]+xregion[i][1]-w) / 2); // g2.drawString(chrid, cx, TOP-12); // } List<CrossChromosome> chromosomes = this.cross.getGenotypeData(); for(int i = 0; i < this.chridx.length; i++) { String chromosomeName = chromosomes.get(this.chridx[i]).getChromosomeName(); w = fim.stringWidth(chromosomeName); int cx = (int)((this.xregion[i][0]+this.xregion[i][1]-w) / 2); this.g2.drawString(chromosomeName, cx, TOP-12); } } // draw title private void drawTitle() { Font f = new Font("SansSerif", Font.BOLD, 14); this.g2.setFont(f); // find title position FontMetrics fim = this.g2.getFontMetrics(f); int w = fim.stringWidth(this.title); int cx = (this.WIDTH-w) / 2; this.g2.drawString(this.title, cx, TOP-40); } /** * Redraw the plot */ public void reDraw() { getFigureProperties(); // sort index sort(this.sortbyidx); // re-calculate plot region calcPlotRegion(); setPreferredSize(new Dimension(this.WIDTH, this.HEIGHT)); // recreate bufferedimage createBufferedImage(); // add mouse listeners if it's interactive plot if(this.interactive) { if(this.getMouseMotionListeners().length == 0) addMouseListener(this.mouselistener); addMouseMotionListener(this); } else { if(this.getMouseMotionListeners().length > 0) this.removeMouseMotionListener(this); this.removeMouseListener(this.mouselistener); } this.repaint(); this.revalidate(); } /** * {@inheritDoc} */ public void mouseDragged(MouseEvent event) {} /** * {@inheritDoc} */ public void mouseMoved(MouseEvent event) { // currently not working for crossover on real marker distance if( (this.whattoplot==1) && this.inMarkDist) { ToolTipManager.sharedInstance().setEnabled(false); return; } // highlight the pointed marker for an individual highLightBox(event.getX(), event.getY()); // show marker and individual name if in plot region if(this.inPlotRegion) { ToolTipManager.sharedInstance().setEnabled(true); showTip(); } else // disable tooltip ToolTipManager.sharedInstance().setEnabled(false); } // inner class to handle mouse event private class MyMouseListener extends MouseAdapter { @Override public void mouseReleased(MouseEvent event) { // if it's popup menu trigger, display a popup menu if(event.isPopupTrigger()) { // TODO add back effect plot and MGD link capability // RightClickMenu pop = new RightClickMenu(event, new MyActionListener()); // if(XSPACE >= MAXELESIZE) // pop.setEnableMenuItem(ActionManager.GENOPLOT_ZOOMIN_ACTION, false); // if(XSPACE <= MINELESIZE) // pop.setEnableMenuItem(ActionManager.GENOPLOT_ZOOMOUT_ACTION, false); // pop.show(event.getComponent(), event.getX(), event.getY()); } else { if(GenoPlot.this.interactive) { if( GenoPlot.this.inPlotRegion ) { // if this is left click, show a dialog box for spot info // if the current figure is interactive SpotInfoDialogBox d = new SpotInfoDialogBox(); d.setVisible(true); } } } } // for unix system @Override public void mousePressed(MouseEvent event) { // TODO same as above todo // if(event.isPopupTrigger()) { // this is a pop up menu trigger // ActionManager manager = qtl.mainFrame.getActionManager(); // RightClickMenu pop = new RightClickMenu(event, new MyActionListener()); // if(XSPACE >= MAXELESIZE) // pop.setEnableMenuItem(ActionManager.GENOPLOT_ZOOMIN_ACTION, false); // if(XSPACE <= MINELESIZE) // pop.setEnableMenuItem(ActionManager.GENOPLOT_ZOOMOUT_ACTION, false); // pop.show(event.getComponent(), event.getX(), event.getY()); // } } } // inner class for handle zoom in/out from right click menu // private class MyActionListener implements ActionListener { // public void actionPerformed(ActionEvent event) { // String command = event.getActionCommand(); // if(command.equals(ActionManager.GENOPLOT_ZOOMIN_ACTION)) // ZoomIn(); // else if(command.equals(ActionManager.GENOPLOT_ZOOMOUT_ACTION)) // ZoomOut(); // } // } // inner class for spot info dialog box private class SpotInfoDialogBox extends JDialog implements ActionListener { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 466801490922375037L; JEditorPane infoDisplayTextPane; int thischridx, markeridx, thisindidx; String infoText; // constructor public SpotInfoDialogBox() { super(QTL.getInstance().getApplicationFrame(), "Spot Information", true); this.thischridx = GenoPlot.this.chridx[GenoPlot.this.thisChrIdx]; this.markeridx = GenoPlot.this.thisMarIdx; this.thisindidx = GenoPlot.this.sortIdx[GenoPlot.this.indidx[GenoPlot.this.thisIndIdx]]; // make the dialog box this.infoDisplayTextPane = new JEditorPane(); this.infoDisplayTextPane.setContentType("text/html"); Font infoDisplayFont = new Font("monospaced", Font.PLAIN, 10); this.infoDisplayTextPane.setFont(infoDisplayFont); this.infoDisplayTextPane.setEditable(false); makeInfoText(); this.infoDisplayTextPane.setText(this.infoText); // make a scroll pane JPanel p = new JPanel(new BorderLayout()); p.add(this.infoDisplayTextPane, BorderLayout.CENTER); p.setBackground(Color.white); // p.setPreferredSize(new Dimension(500, 480)); JScrollPane infoScrollPane = new JScrollPane(p); // infoScrollPane.add(infoDisplayTextPane); // infoScrollPane.add(profile); infoScrollPane.setPreferredSize(new Dimension(450, 400)); /* JPanel p = new JPanel(); p.add(infoScrollPane, "North"); p.add(profile, "South") */ JButton okButton = new JButton("Ok"); okButton.addActionListener(this); okButton.setPreferredSize(new Dimension(80, 30)); JPanel buttonPanel = new JPanel(); buttonPanel.add(okButton); buttonPanel.setBorder(BorderFactory.createEmptyBorder(10,0,10,0)); getContentPane().add(infoScrollPane, "North"); getContentPane().add(buttonPanel, "South"); // center the window Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = this.getPreferredSize(); setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); pack(); } public void actionPerformed(ActionEvent e) { dispose(); } //Overridden so we can exit when window is closed @Override protected void processWindowEvent(WindowEvent e) { if (e.getID() == WindowEvent.WINDOW_CLOSING) { dispose(); } super.processWindowEvent(e); } // make info text private void makeInfoText() { CrossChromosome chr = GenoPlot.this.cross.getGenotypeData().get(this.thischridx); // TODO this should be sensitive to sex specific maps List<GeneticMarker> markerPositions = chr.getAnyGeneticMap().getMarkerPositions(); NamedCategoricalData selectedMarkerGenotypes = chr.getMarkerGenotypes().get( this.markeridx); NamedDataMatrix<Number> phenotypeData = GenoPlot.this.cross.getPhenotypeData(); // genotype info this.infoText = "<center><H2>Genotype Information</H2></center>" + "<table border=1 width=400 align=center>"; // chr number this.infoText += "<tr><td>Chromosome</td><td>" + chr.getChromosomeName() + "</td></tr>"; // marker name this.infoText += "<tr><td>Marker Name</td><td>" + chr.getMarkerNames()[this.markeridx] + "</td></tr>"; // marker position this.infoText += "<tr><td>Marker Position (cM)</td><td>" + markerPositions.get(this.markeridx).getMarkerPositionCentimorgans() + "</td></tr>"; // genotype String genoStr = selectedMarkerGenotypes.integerToCategoryString( (Integer)selectedMarkerGenotypes.getData().get(this.thisindidx)); this.infoText += "<tr><td>Genotype</td><td>" + genoStr + "</td></tr>"; // error LOD if(chr.getErrorLodsExist()) { Number errorLods = chr.getMarkerErrorLods().get(this.markeridx).getData().get(this.thisindidx); if(errorLods != null) { this.infoText = this.infoText + "<tr><td>Error LOD</td><td>" + errorLods.toString() + "</td></tr>"; } } this.infoText += "</table>"; // individual info this.infoText = this.infoText + "<center><H2>Phenotype Information</H2></center>" + "<table border=1 width=400 align=center>"; List<NamedData<Number>> phenotypeDataList = phenotypeData.getNamedDataList(); for(NamedData<Number> namedData: phenotypeDataList) { String currStringValue; if(namedData instanceof NamedCategoricalData) { // if it's categorical data use the category name NamedCategoricalData namedCategoricalData = (NamedCategoricalData)namedData; currStringValue = namedCategoricalData.getCategoryStringAt( this.thisindidx); if(currStringValue == null) { currStringValue = "Missing"; } } else { Number currValue = namedData.getData().get(this.thisindidx); currStringValue = currValue == null ? "Missing" : currValue.toString(); } this.infoText = this.infoText + "<tr><td>" + namedData.getNameOfData() + "</td><td>" + currStringValue + "</td></tr>"; } this.infoText = this.infoText + "</table>"; } } }