Skip to content
Snippets Groups Projects
JamView.java 55.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* JamView
     *
     * created: 2009
     *
     * This file is part of Artemis
     *
     * Copyright(C) 2009  Genome Research Limited
     *
     * This program 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 2
     * of the License, or(at your option) any later version.
     *
     * This program 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 program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     *
     */
    
    tjc's avatar
    tjc committed
    package uk.ac.sanger.artemis.components.alignment;
    
    
    import java.awt.AlphaComposite;
    
    tjc's avatar
    tjc committed
    import java.awt.BasicStroke;
    import java.awt.BorderLayout;
    import java.awt.Color;
    
    import java.awt.Composite;
    
    tjc's avatar
    tjc committed
    import java.awt.Cursor;
    
    tjc's avatar
    tjc committed
    import java.awt.Dimension;
    
    tjc's avatar
    tjc committed
    import java.awt.FlowLayout;
    
    tjc's avatar
    tjc committed
    import java.awt.FontMetrics;
    
    tjc's avatar
    tjc committed
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    
    import java.awt.Insets;
    
    tjc's avatar
    tjc committed
    import java.awt.Point;
    
    tjc's avatar
    tjc committed
    import java.awt.Rectangle;
    
    tjc's avatar
    tjc committed
    import java.awt.Stroke;
    
    tjc's avatar
    tjc committed
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    tjc's avatar
    tjc committed
    import java.awt.event.AdjustmentEvent;
    import java.awt.event.AdjustmentListener;
    
    tjc's avatar
    tjc committed
    import java.awt.event.FocusEvent;
    import java.awt.event.FocusListener;
    import java.awt.event.ItemEvent;
    import java.awt.event.ItemListener;
    import java.awt.event.KeyAdapter;
    import java.awt.event.KeyEvent;
    
    tjc's avatar
    tjc committed
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    
    import java.awt.event.MouseMotionListener;
    
    tjc's avatar
    tjc committed
    import java.awt.event.WindowEvent;
    import java.awt.event.WindowFocusListener;
    
    tjc's avatar
    tjc committed
    import java.io.File;
    
    tjc's avatar
    tjc committed
    import java.util.Collections;
    
    tjc's avatar
    tjc committed
    import java.util.Enumeration;
    
    tjc's avatar
    tjc committed
    import java.util.Hashtable;
    import java.util.List;
    import java.util.Vector;
    
    
    import javax.swing.ButtonGroup;
    
    tjc's avatar
    tjc committed
    import javax.swing.JButton;
    
    tjc's avatar
    tjc committed
    import javax.swing.JCheckBox;
    
    tjc's avatar
    tjc committed
    import javax.swing.JCheckBoxMenuItem;
    
    tjc's avatar
    tjc committed
    import javax.swing.JComboBox;
    
    tjc's avatar
    tjc committed
    import javax.swing.JComponent;
    
    tjc's avatar
    tjc committed
    import javax.swing.JFrame;
    
    import javax.swing.JMenu;
    
    tjc's avatar
    tjc committed
    import javax.swing.JMenuBar;
    
    tjc's avatar
    tjc committed
    import javax.swing.JOptionPane;
    
    tjc's avatar
    tjc committed
    import javax.swing.JPanel;
    
    import javax.swing.JPopupMenu;
    
    tjc's avatar
    tjc committed
    import javax.swing.JScrollBar;
    
    tjc's avatar
    tjc committed
    import javax.swing.JScrollPane;
    
    import javax.swing.JSeparator;
    
    tjc's avatar
    tjc committed
    import javax.swing.JTextField;
    
    tjc's avatar
    tjc committed
    import javax.swing.Scrollable;
    import javax.swing.SwingConstants;
    import javax.swing.UIManager;
    
    import javax.swing.border.Border;
    import javax.swing.border.EmptyBorder;
    
    tjc's avatar
    tjc committed
    
    
    import net.sf.samtools.AlignmentBlock;
    
    tjc's avatar
    tjc committed
    import net.sf.samtools.SAMFileHeader;
    import net.sf.samtools.SAMFileReader;
    import net.sf.samtools.SAMRecord;
    import net.sf.samtools.SAMSequenceRecord;
    import net.sf.samtools.SAMFileReader.ValidationStringency;
    import net.sf.samtools.util.CloseableIterator;
    
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.Entry;
    import uk.ac.sanger.artemis.EntryGroup;
    import uk.ac.sanger.artemis.Options;
    
    import uk.ac.sanger.artemis.Selection;
    import uk.ac.sanger.artemis.SelectionChangeEvent;
    import uk.ac.sanger.artemis.SelectionChangeListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.SimpleEntryGroup;
    
    import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
    import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.components.EntryFileDialog;
    
    import uk.ac.sanger.artemis.components.FeatureDisplay;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.components.MessageDialog;
    
    import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.EntryInformation;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.Range;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.Bases;
    
    import uk.ac.sanger.artemis.sequence.MarkerRange;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.NoSequenceException;
    import uk.ac.sanger.artemis.util.Document;
    import uk.ac.sanger.artemis.util.DocumentFactory;
    import uk.ac.sanger.artemis.util.OutOfRangeException;
    
    public class JamView extends JPanel
    
                         implements Scrollable, DisplayAdjustmentListener, SelectionChangeListener
    
    tjc's avatar
    tjc committed
    {
      private static final long serialVersionUID = 1L;
    
    tjc's avatar
    tjc committed
      private List<SAMRecord> readsInView;
    
    tjc's avatar
    tjc committed
      private Hashtable<String, Integer> seqLengths = new Hashtable<String, Integer>();
      private Vector<String> seqNames = new Vector<String>();
      private String bam;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      private Bases bases;
    
    tjc's avatar
    tjc committed
      private JScrollPane jspView;
    
    tjc's avatar
    tjc committed
      private JScrollBar scrollBar;
      
    
    tjc's avatar
    tjc committed
      private JComboBox combo;
    
    tjc's avatar
    tjc committed
      private boolean isSingle = false;
      private boolean isSNPs = false;
      private boolean isStackView = false;
    
      private boolean isPairedStackView = false;
    
      private boolean isCoverage = false;
    
    tjc's avatar
    tjc committed
      
    
      private FeatureDisplay feature_display;
      private Selection selection;
      private JPanel mainPanel;
    
      private CoveragePanel coveragePanel;
    
      private boolean showScale = true;
    
    tjc's avatar
    tjc committed
      private Ruler ruler = new Ruler();
    
    tjc's avatar
    tjc committed
      private int nbasesInView;
    
      
      private int startBase = -1;
      private int endBase   = -1;
    
    tjc's avatar
    tjc committed
      private int laststart;
      private int lastend;
    
      private int maxUnitIncrement = 8;
    
    tjc's avatar
    tjc committed
      private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
      private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
    
      
      private boolean showBaseAlignment = false;
    
        AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
      
    
      /** Used to colour the frames. */
    
      private Color lightGrey = new Color(200, 200, 200);
      private Color darkGreen = new Color(0, 150, 0);
    
      private Color darkOrange = new Color(255,140,0);
      
    
      private Point lastMousePoint = null;
      private SAMRecord mouseOverSAMRecord = null;
    
      // record of where a mouse drag starts
      private int dragStart = -1;
    
      private int maxHeight = 800;
    
    tjc's avatar
    tjc committed
      private boolean concatSequences = false;
    
    tjc's avatar
    tjc committed
      private int ALIGNMENT_PIX_PER_BASE;
    
    tjc's avatar
    tjc committed
      
    
      private JPopupMenu popup;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
     
      public JamView(String bam, 
                     String reference,
                     int nbasesInView)
      {
        super();
        setBackground(Color.white);
        this.bam = bam;
        this.nbasesInView = nbasesInView;
        
        if(reference != null)
        {
    
    tjc's avatar
    tjc committed
          EntryGroup entryGroup = new SimpleEntryGroup();
    
    tjc's avatar
    tjc committed
          try
          {
            getEntry(reference,entryGroup);
          }
          catch (NoSequenceException e)
          {
            e.printStackTrace();
          }
        }
    
    tjc's avatar
    tjc committed
        
    
    tjc's avatar
    tjc committed
        readHeaderPicard();
    
    tjc's avatar
    tjc committed
        
    
    tjc's avatar
    tjc committed
        // set font size
    
        //setFont(getFont().deriveFont(12.f));
    
    tjc's avatar
    tjc committed
    
    
        //Options.getOptions().getFontUIResource();
        //final javax.swing.plaf.FontUIResource font_ui_resource =
        //  new javax.swing.plaf.FontUIResource(getFont());
        final javax.swing.plaf.FontUIResource font_ui_resource =
          Options.getOptions().getFontUIResource();
        
    
    tjc's avatar
    tjc committed
        Enumeration<Object> keys = UIManager.getDefaults().keys();
    
    tjc's avatar
    tjc committed
        while(keys.hasMoreElements()) 
        {
          Object key = keys.nextElement();
          Object value = UIManager.get(key);
          if(value instanceof javax.swing.plaf.FontUIResource) 
            UIManager.put(key, font_ui_resource);
        }
    
        
        setFont(Options.getOptions().getFont());
    
    tjc's avatar
    tjc committed
        FontMetrics fm  = getFontMetrics(getFont());
    
        ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
        selection = new Selection(null);
    
        
        MultiLineToolTipUI.initialize();
        setToolTipText("");
      }
      
      public String getToolTipText()
      {
        return ( mouseOverSAMRecord != null ? 
            mouseOverSAMRecord.getReadName() + "\n" + 
            mouseOverSAMRecord.getAlignmentStart() + ".." +
            mouseOverSAMRecord.getAlignmentEnd() + "\nisize=" +
    
    tjc's avatar
    tjc committed
            mouseOverSAMRecord.getInferredInsertSize() + "\nrname=" +
            mouseOverSAMRecord.getReferenceName(): null);
    
    tjc's avatar
    tjc committed
      }
    
    tjc's avatar
    tjc committed
      private void readHeader()
      {
    
    tjc's avatar
    tjc committed
        String samtoolCmd = "";
        if(System.getProperty("samtoolDir") != null)
          samtoolCmd = System.getProperty("samtoolDir");
        String cmd[] = { samtoolCmd+File.separator+"samtools",  
    
    tjc's avatar
    tjc committed
    			     "view", "-H", bam };
    	
    
    tjc's avatar
    tjc committed
        RunSamTools samtools = new RunSamTools(cmd, null, null, null);
    
    tjc's avatar
    tjc committed
    	
        if(samtools.getProcessStderr() != null)
          System.out.println(samtools.getProcessStderr());
     
        String header = samtools.getProcessStdout();
        
        StringReader samReader = new StringReader(header);
    	BufferedReader buff = new BufferedReader(samReader);
        
    	String line;
    	try 
    	{
    	  while((line = buff.readLine()) != null)
    	  {
    	    if(line.indexOf("LN:") > -1)
    	    {
    	      String parts[] = line.split("\t");
    	      String name = "";
    	      int seqLength = 0;
    	      for(int i=0; i<parts.length; i++)
    	      {
    	        if(parts[i].startsWith("LN:"))
    	          seqLength = Integer.parseInt( parts[i].substring(3) );
    	        else if(parts[i].startsWith("SN:"))
    	          name = parts[i].substring(3);
    	      }
    	      seqLengths.put(name, seqLength);
    	      seqNames.add(name);
    	    }
    	  }
    	} 
    	catch (IOException e) 
    	{
    	  e.printStackTrace();
    	}
      }
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      private void readFromBam(int start, int end)
    
    tjc's avatar
    tjc committed
      {
        String refName = (String) combo.getSelectedItem();
    
    tjc's avatar
    tjc committed
        
        String samtoolCmd = "";
        if(System.getProperty("samtoolDir") != null)
          samtoolCmd = System.getProperty("samtoolDir");
    
    tjc's avatar
    tjc committed
        
    
    tjc's avatar
    tjc committed
    	String cmd[] = { samtoolCmd+File.separator+"samtools",  
    
    tjc's avatar
    tjc committed
    				     "view", 
    				     bam, refName+":"+start+"-"+end };
    
    tjc's avatar
    tjc committed
    		
    	for(int i=0; i<cmd.length;i++)
    	  System.out.print(cmd[i]+" ");
    	System.out.println();
    
    tjc's avatar
    tjc committed
    	
        if(readsInView == null)
    
    tjc's avatar
    tjc committed
          readsInView = new Vector<SAMRecord>();
    
    tjc's avatar
    tjc committed
        else
          readsInView.clear();
    	RunSamTools samtools = new RunSamTools(cmd, null, null, readsInView);
    
    tjc's avatar
    tjc committed
    		
    	if(samtools.getProcessStderr() != null)
          System.out.println(samtools.getProcessStderr());
    	    
    
    tjc's avatar
    tjc committed
    	samtools.waitForStdout();
    
    tjc's avatar
    tjc committed
      }*/
      
    
      private void readHeaderPicard()
      {
        File bamFile = new File(bam);
        File indexFile = new File(bam+".bai");
        final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
        SAMFileHeader header = inputSam.getFileHeader();
        List<SAMSequenceRecord> readGroups = header.getSequenceDictionary().getSequences();
        
        for(int i=0; i<readGroups.size(); i++)
        {
          seqLengths.put(readGroups.get(i).getSequenceName(),
                         readGroups.get(i).getSequenceLength());
          seqNames.add(readGroups.get(i).getSequenceName());
        }
        inputSam.close();
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * Read a SAM or BAM file.
       */
      private void readFromBamPicard(int start, int end)
      {
        // Open the input file.  Automatically detects whether input is SAM or BAM
        // and delegates to a reader implementation for the appropriate format.
        File bamFile = new File(bam);
        File indexFile = new File(bam+".bai");
        final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
        inputSam.setValidationStringency(ValidationStringency.SILENT);
        
        if(readsInView == null)
          readsInView = new Vector<SAMRecord>();
        else
          readsInView.clear();
    
    tjc's avatar
    tjc committed
    
        if(concatSequences)
        {
          int len = 0;
          int lastLen = 1;
          for(int i=0; i<seqNames.size(); i++)
          {
            int thisLength = seqLengths.get(seqNames.get(i));
            len += thisLength;
    
            if( (lastLen >= start && lastLen < end) ||
                (len >= start && len < end) ||
                (start >= lastLen && start < len) ||
                (end >= lastLen && end < len) )
            {
              int offset = getSequenceOffset(seqNames.get(i)); 
              int thisStart = start - offset;
              if(thisStart < 1)
                thisStart = 1;
              int thisEnd   = end - offset;
              if(thisEnd > thisLength)
                thisEnd = thisLength;
              
              System.out.println("READ "+seqNames.get(i)+"  "+thisStart+".."+thisEnd);
              iterateOverBam(inputSam, seqNames.get(i), thisStart, thisEnd);
            }
            lastLen = len;
          }
        }
        else
        {
          String refName = (String) combo.getSelectedItem();
          iterateOverBam(inputSam, refName, start, end);
        }
        
        inputSam.close();
        //System.out.println("readFromBamPicard "+start+".."+end);
      }
      
      /**
       * Iterate over BAM file and load into the <code>List</code> of
       * <code>SAMRecord</code>.
       * @param inputSam
       * @param refName
       * @param start
       * @param end
       */
      private void iterateOverBam(final SAMFileReader inputSam, 
                                  String refName, int start, int end)
      {
    
    tjc's avatar
    tjc committed
        CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
        while ( it.hasNext() )
        {
          try
          {
            SAMRecord samRecord = it.next();
            readsInView.add(samRecord);
          }
          catch(Exception e)
          {
            System.out.println(e.getMessage());
          }
        }
    
    tjc's avatar
    tjc committed
        it.close();
      }
    
      private int getSequenceLength()
      {
        if(concatSequences)
        {
          int len = 0;
          for(int i=0; i<seqNames.size(); i++)
            len += seqLengths.get(seqNames.get(i));
          return len;
        }
        else
          return seqLengths.get((String) combo.getSelectedItem());
      }
      
      protected int getSequenceOffset(String refName)
      {
        if(!concatSequences)
          return 0;
        int offset = 0;
        for(int i=0; i<combo.getItemCount(); i++)
        {
          String thisSeqName = (String) combo.getItemAt(i);
          if(refName.equals(thisSeqName))
            return offset;
          
          offset += seqLengths.get(combo.getItemAt(i));
        }
        return offset;
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * Override
       */
    
      protected void paintComponent(Graphics g)
    
    tjc's avatar
    tjc committed
      {
    	super.paintComponent(g);
    	Graphics2D g2 = (Graphics2D)g;
    
    
    	mouseOverSAMRecord = null;
    
    tjc's avatar
    tjc committed
        int seqLength = getSequenceLength();
    
    tjc's avatar
    tjc committed
    	float pixPerBase = getPixPerBaseByWidth();
    
    tjc's avatar
    tjc committed
    	
    
        int start;
    
    tjc's avatar
    tjc committed
        int end;
    
        
        if(startBase > 0)
          start = startBase;
        else
    
    tjc's avatar
    tjc committed
          start = getBaseAtStartOfView();
    
        
        if(endBase > 0)
          end = endBase;
        else
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          end   = start + nbasesInView - 1;
          if(end > seqLength)
            end = seqLength;
    
    tjc's avatar
    tjc committed
        }
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
    
        //System.out.println(start+".."+end+" " +
        //		"sequence length = "+getSequenceLength()+
        //		" pixPerBase="+pixPerBase);
    
    tjc's avatar
    tjc committed
          
    
    tjc's avatar
    tjc committed
        if(laststart != start ||
    
    tjc's avatar
    tjc committed
           lastend   != end)
        {
    
    tjc's avatar
    tjc committed
          setCursor(cbusy);
    
    tjc's avatar
    tjc committed
          try
          {
    
    tjc's avatar
    tjc committed
            readFromBamPicard(start, end);
    
    tjc's avatar
    tjc committed
            if(!isStackView || pixPerBase*3 >= ALIGNMENT_PIX_PER_BASE)
    
              Collections.sort(readsInView, new SAMRecordComparator());
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            setCursor(cdone);
    
    tjc's avatar
    tjc committed
          }
          catch(OutOfMemoryError ome)
          {
            JOptionPane.showMessageDialog(this, "Out of Memory");
            return;
          }
        }
    
    tjc's avatar
    tjc committed
        
        laststart = start;
        lastend   = end;
    
    tjc's avatar
    tjc committed
    	if(pixPerBase*3 >= ALIGNMENT_PIX_PER_BASE)
    
    tjc's avatar
    tjc committed
    	  drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
    	else
    
    	    drawStackView(g2, seqLength, pixPerBase, start, end);
    
    	  else if(isPairedStackView)
    	    drawPairedStackView(g2, seqLength, pixPerBase, start, end);
    	  else
    	    drawLineView(g2, seqLength, pixPerBase, start, end);
    
    	  if(isCoverage)
    	  {
    	    coveragePanel.setStartAndEnd(start, end);
    	    coveragePanel.setPixPerBase(pixPerBase);
    	    coveragePanel.repaint();
    	  }
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      
      private float getPixPerBaseByWidth()
      {
    
    tjc's avatar
    tjc committed
        return (float)( getPreferredSize().getWidth() /nbasesInView);
    
    tjc's avatar
    tjc committed
      }
      
      
    
    tjc's avatar
    tjc committed
      private int getMaxBasesInPanel(int seqLength)
    
      {
        if(feature_display == null)
    
    tjc's avatar
    tjc committed
          return seqLength+nbasesInView/2;
    
    tjc's avatar
    tjc committed
          return seqLength+nbasesInView;
    
    tjc's avatar
    tjc committed
      
    
    tjc's avatar
    tjc committed
      /**
       * Draw the zoomed-in base view.
       * @param g2
       * @param seqLength
       * @param pixPerBase
       * @param start
       * @param end
       */
    
      private void drawBaseAlignment(Graphics2D g2, 
                                     int seqLength, 
                                     float pixPerBase, 
                                     final int start, 
                                     final int end)
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        ruler.start = start;
        ruler.end = end;
        ruler.repaint();
        
    
        int ypos = 0;
    
        String refSeq = null;
    
    tjc's avatar
    tjc committed
        int refSeqStart = start;
    
    tjc's avatar
    tjc committed
        if(bases != null)
        {
          // draw the reference sequence
          ypos+=11;
    
    tjc's avatar
    tjc committed
          try
          {
    
            int seqEnd = end+2;
    
    tjc's avatar
    tjc committed
            if(seqEnd > bases.getLength())
              seqEnd = bases.getLength();
    
    tjc's avatar
    tjc committed
    
            if(refSeqStart < 1)
              refSeqStart = 1;
            refSeq = 
    
              bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
    
    tjc's avatar
    tjc committed
            
            System.out.println(refSeqStart +" "+ seqEnd+" "+refSeq);
    
    tjc's avatar
    tjc committed
            g2.fillRect(0, ypos-11, getPreferredSize().width, 11);
    
            drawSelectionRange(g2, pixPerBase, start, end);
    
            g2.setColor(Color.black);
    
    tjc's avatar
    tjc committed
            g2.drawString(refSeq, 0, ypos);
    
    tjc's avatar
    tjc committed
          }
          catch (OutOfRangeException e)
          {
            e.printStackTrace();
          }
        }
    
        else
          drawSelectionRange(g2, pixPerBase, start, end);
    
    tjc's avatar
    tjc committed
        
    
    tjc's avatar
    tjc committed
        boolean drawn[] = new boolean[readsInView.size()];
    
    tjc's avatar
    tjc committed
        for(int i=0; i<readsInView.size(); i++)
    
    tjc's avatar
    tjc committed
          drawn[i] = false;
    
    tjc's avatar
    tjc committed
        
    
    tjc's avatar
    tjc committed
        for(int i=0; i<readsInView.size(); i++)
        {
    
    tjc's avatar
    tjc committed
          if (!drawn[i])
    
    tjc's avatar
    tjc committed
          {
    
    tjc's avatar
    tjc committed
            SAMRecord thisRead = readsInView.get(i);
    
    tjc's avatar
    tjc committed
            ypos+=11;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            drawSequence(g2, thisRead, pixPerBase, ypos, refSeq, refSeqStart);
            drawn[i] = true;
            
            int thisEnd = thisRead.getAlignmentEnd();
            if(thisEnd == 0)
              thisEnd = thisRead.getAlignmentStart()+thisRead.getReadLength();
    
    tjc's avatar
    tjc committed
            
            for(int j=i+1; j<readsInView.size(); j++)
            {
    
    tjc's avatar
    tjc committed
              if (!drawn[j])
    
    tjc's avatar
    tjc committed
              {
    
    tjc's avatar
    tjc committed
                SAMRecord nextRead = readsInView.get(j);
                if(nextRead.getAlignmentStart() > thisEnd+1)
    
    tjc's avatar
    tjc committed
                {
    
    tjc's avatar
    tjc committed
                  drawSequence(g2, nextRead, pixPerBase, ypos, refSeq, refSeqStart);
                  drawn[j] = true;
                  thisEnd = nextRead.getAlignmentEnd();
                  if(thisEnd == 0)
                    thisEnd = nextRead.getAlignmentStart()+nextRead.getReadLength();
    
    tjc's avatar
    tjc committed
                }
              }
            }
          }
        }
    
    tjc's avatar
    tjc committed
        
        if(ypos > getHeight())
        {
    
    tjc's avatar
    tjc committed
          Dimension d = getPreferredSize();
          d.setSize(getPreferredSize().getWidth(), ypos);
          setPreferredSize(d);
    
    tjc's avatar
    tjc committed
          revalidate();
        }
    
    tjc's avatar
    tjc committed
      }
    
    tjc's avatar
    tjc committed
      
    
    tjc's avatar
    tjc committed
      /**
       * Draw the query sequence
       * @param g2
       * @param read
       * @param pixPerBase
       * @param ypos
       */
    
    tjc's avatar
    tjc committed
      private void drawSequence(Graphics2D g2, SAMRecord samRecord, 
    
                                float pixPerBase, int ypos, String refSeq, int refSeqStart)
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        if (!samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
            samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped 
    
    tjc's avatar
    tjc committed
          g2.setColor(Color.black);
        else
          g2.setColor(Color.blue);
        
    
    tjc's avatar
    tjc committed
        Color col = g2.getColor();
    
    tjc's avatar
    tjc committed
        int xpos;
    
        int len = 0;
    
        String readSeq = samRecord.getReadString();
    
    tjc's avatar
    tjc committed
        int offset = getSequenceOffset(samRecord.getReferenceName());
        
    
        List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
        for(int i=0; i<blocks.size(); i++)
    
    tjc's avatar
    tjc committed
        {
    
          AlignmentBlock block = blocks.get(i);
    
          len += block.getLength();
    
          for(int j=0; j<block.getLength(); j++)
    
    tjc's avatar
    tjc committed
          {
    
            int readPos = block.getReadStart()-1+j;
    
    tjc's avatar
    tjc committed
            xpos  = block.getReferenceStart()-1+j+offset;
    
            int refPos = xpos-refSeqStart+1;
    
    tjc's avatar
    tjc committed
            if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
    
              if(readSeq.charAt(readPos) != refSeq.charAt(refPos))
    
                g2.setColor(Color.red);
              else
                g2.setColor(col);
            }
    
    tjc's avatar
    tjc committed
            g2.drawString(readSeq.substring(readPos, readPos+1), 
                          refPos*ALIGNMENT_PIX_PER_BASE, ypos);
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
        }
    
        
        if(lastMousePoint != null)
        {
    
    tjc's avatar
    tjc committed
          int refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
          int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
          int xend   = (refPos+len)*ALIGNMENT_PIX_PER_BASE;
    
    
          if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
          if(lastMousePoint.getX() > xstart &&
             lastMousePoint.getX() < xend)
          {
            mouseOverSAMRecord = samRecord;    
          }
        }
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * Draw zoomed-out view.
       * @param g2
       * @param seqLength
       * @param pixPerBase
       * @param start
       * @param end
       */
    
    tjc's avatar
    tjc committed
      private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
    
      {
        drawSelectionRange(g2, pixPerBase,start, end);
        if(isShowScale())
          drawScale(g2, start, end, pixPerBase);
    
    tjc's avatar
    tjc committed
        
    
        Stroke originalStroke =
          new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 
    
    tjc's avatar
    tjc committed
        Stroke stroke =
    
          new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
    
        int scaleHeight;
        if(isShowScale())
          scaleHeight = 15;
        else
          scaleHeight = 0;
    
    tjc's avatar
    tjc committed
        
        for(int i=0; i<readsInView.size(); i++)
        {
    
    tjc's avatar
    tjc committed
          SAMRecord samRecord = readsInView.get(i);
          SAMRecord samNextRecord = null;      
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
    
    tjc's avatar
    tjc committed
              samRecord.getMateUnmappedFlag() )  // mate is unmapped
    
    tjc's avatar
    tjc committed
          {
    
    tjc's avatar
    tjc committed
            if(isSingle)
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              int ypos = (getHeight() - scaleHeight) - samRecord.getReadString().length();
    
    tjc's avatar
    tjc committed
              g2.setColor(Color.orange);
    
    tjc's avatar
    tjc committed
              drawRead(g2, samRecord, pixPerBase, stroke, ypos);
    
    tjc's avatar
    tjc committed
            }
    
    tjc's avatar
    tjc committed
            continue;
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          int ypos = (getHeight() - scaleHeight) - ( Math.abs(samRecord.getInferredInsertSize()) );
    
    tjc's avatar
    tjc committed
          if(i < readsInView.size()-1)
          {
    
    tjc's avatar
    tjc committed
            samNextRecord = readsInView.get(++i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(samRecord.getReadName().equals(samNextRecord.getReadName()))
    
    tjc's avatar
    tjc committed
            { 
              // draw connection between paired reads
    
    tjc's avatar
    tjc committed
              if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() && 
                  (samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
    
    tjc's avatar
    tjc committed
              {
                g2.setStroke(originalStroke);
                g2.setColor(Color.LIGHT_GRAY);
    
    tjc's avatar
    tjc committed
                       (int)((samRecord.getAlignmentEnd()-getBaseAtStartOfView())*pixPerBase), 
                       (int)((samNextRecord.getAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
    
    tjc's avatar
    tjc committed
              }
    
    tjc's avatar
    tjc committed
              
              if( samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
                  samNextRecord.getReadNegativeStrandFlag() )
                g2.setColor(Color.red);
              else
                g2.setColor(Color.blue);
              
              drawRead(g2, samRecord, pixPerBase, stroke, ypos);
              drawRead(g2, samNextRecord, pixPerBase, stroke, ypos);
    
    tjc's avatar
    tjc committed
            }
            else
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
    
    tjc's avatar
    tjc committed
              i--;
    
    tjc's avatar
    tjc committed
            }
          }
          else
          {
    
    tjc's avatar
    tjc committed
            drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
        }
    
    tjc's avatar
    tjc committed
        drawYScale(g2, scaleHeight);
    
    tjc's avatar
    tjc committed
      }
      
    
      /**
       * Draw the reads as lines in vertical stacks. The reads are colour 
       * coded as follows:
       * 
       * blue  - reads are unique and are paired with a mapped mate
       * black - reads are unique and are not paired or have an unmapped mate
    
       * green - reads are duplicates
    
       * 
       * @param g2
       * @param seqLength
       * @param pixPerBase
       * @param start
       * @param end
       */
    
      private void drawStackView(Graphics2D g2, 
                                 int seqLength, 
                                 float pixPerBase, 
                                 int start, 
                                 int end)
      {
        drawSelectionRange(g2, pixPerBase,start, end);
        if(isShowScale())
          drawScale(g2, start, end, pixPerBase);
        
        BasicStroke stroke = new BasicStroke(
            1.3f,
            BasicStroke.CAP_BUTT, 
            BasicStroke.JOIN_MITER);
        
        int scaleHeight;
        if(isShowScale())
          scaleHeight = 15;
        else
          scaleHeight = 0;
        
        int ypos = (getHeight() - scaleHeight);
    
        int maxEnd = 0;
        int lstStart = 0;
        int lstEnd = 0;
    
        
        g2.setColor(Color.blue);
        for(int i=0; i<readsInView.size(); i++)
        {
          SAMRecord samRecord = readsInView.get(i);
    
    tjc's avatar
    tjc committed
          int recordStart = samRecord.getAlignmentStart();
    
          int recordEnd = samRecord.getAlignmentEnd();
    
          if(lstStart != recordStart || lstEnd != recordEnd)
          { 
            if (!samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
                samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped 
              g2.setColor(Color.black);
            else
              g2.setColor(Color.blue);
            
            if(maxEnd < recordStart)
            {
              ypos = (getHeight() - scaleHeight)-2;
              maxEnd = recordEnd+2;
            }
            else
              ypos = ypos-2;
    
          lstStart = recordStart;
          lstEnd   = recordEnd;
          drawRead(g2, samRecord, pixPerBase, stroke, ypos);
    
      
      /**
       * Draw the reads as lines in vertical stacks. The reads are colour 
       * coded as follows:
       * 
       * blue  - reads are unique and are paired with a mapped mate
       * black - reads are unique and are not paired or have an unmapped mate
       * green - reads are duplicates
       * 
       * @param g2
       * @param seqLength
       * @param pixPerBase
       * @param start
       * @param end
       */
      private void drawPairedStackView(Graphics2D g2, 
                                       int seqLength, 
                                       float pixPerBase, 
                                       int start, 
                                       int end)
      {
        drawSelectionRange(g2, pixPerBase,start, end);
        if(isShowScale())
          drawScale(g2, start, end, pixPerBase);
        
        Vector<PairedRead> pairedReads = new Vector<PairedRead>();
        for(int i=0; i<readsInView.size(); i++)
        {
          SAMRecord samRecord = readsInView.get(i);
    
          if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
              samRecord.getMateUnmappedFlag() )  // mate is unmapped
            continue;
    
          SAMRecord samNextRecord = null;      
          if(i < readsInView.size()-1)
          {
            samNextRecord = readsInView.get(++i);
            PairedRead pr = new PairedRead();
            if(samRecord.getReadName().equals(samNextRecord.getReadName()))
            { 
              if(samRecord.getAlignmentStart() < samNextRecord.getAlignmentStart())
              {
                pr.sam1 = samRecord;
                pr.sam2 = samNextRecord;
              }
              else
              {
                pr.sam2 = samRecord;
                pr.sam1 = samNextRecord;
              }
              
            }
            else
            {
              --i;
              pr.sam1 = samRecord;
              pr.sam2 = null;
            }
            pairedReads.add(pr);
          }
        }
        Collections.sort(pairedReads, new PairedReadComparator());
        
        Stroke originalStroke = new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 
        Stroke stroke =
                new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
        int scaleHeight;
        if(isShowScale())
          scaleHeight = 15;
        else
          scaleHeight = 0;
        
        int ypos = getHeight() - scaleHeight - 3;
        int lastEnd = 0;
        
        for(int i=0; i<pairedReads.size(); i++)
        {
          PairedRead pr = pairedReads.get(i);
          
          if(pr.sam1.getAlignmentStart() > lastEnd)
          {
            ypos = getHeight() - scaleHeight - 3;
            
            if(pr.sam2 != null)
            {  
              lastEnd = pr.sam2.getAlignmentEnd();
            }
            else
              lastEnd = pr.sam1.getAlignmentEnd();
          }
          else
            ypos = ypos - 3;
          
          g2.setStroke(originalStroke);
          g2.setColor(Color.LIGHT_GRAY);
          
          if(pr.sam2 != null)
          {
            drawTranslucentJointedLine(g2, 
    
    tjc's avatar
    tjc committed
                    (int)((pr.sam1.getAlignmentEnd()-getBaseAtStartOfView())*pixPerBase),
                    (int)((pr.sam2.getAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
    
          }
          else
          {
            if(!pr.sam1.getMateUnmappedFlag())
            {
              int prStart;
              
              if(pr.sam1.getAlignmentStart() > pr.sam1.getMateAlignmentStart())
                prStart = pr.sam1.getAlignmentEnd();
              else
                prStart = pr.sam1.getAlignmentStart();
                
              drawTranslucentJointedLine(g2, 
    
    tjc's avatar
    tjc committed
                  (int)( (prStart-getBaseAtStartOfView())*pixPerBase),
                  (int)( (pr.sam1.getMateAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
    
            }
          }
          
          if( pr.sam1.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
              ( pr.sam2 != null && pr.sam2.getReadNegativeStrandFlag() ) )
            g2.setColor(Color.red);
          else
            g2.setColor(Color.blue);
          drawRead(g2, pr.sam1, pixPerBase, stroke, ypos);
          
          if(pr.sam2 != null)
            drawRead(g2, pr.sam2, pixPerBase, stroke, ypos);
        }
      }
      
      
    
    tjc's avatar
    tjc committed
      /**
       * Draw a read that apparently has a read mate that is not in view.
       * @param g2
       * @param thisRead
       * @param ypos
       * @param pixPerBase
       * @param originalStroke
       * @param stroke
       */
    
    tjc's avatar
    tjc committed
      private void drawLoneRead(Graphics2D g2, SAMRecord samRecord, int ypos, 
    
    tjc's avatar
    tjc committed
          float pixPerBase, Stroke originalStroke, Stroke stroke)
      {
    
        boolean offTheTop = false;
    
    tjc's avatar
    tjc committed
        int offset = getSequenceOffset(samRecord.getReferenceName());
        int thisStart = samRecord.getAlignmentStart()-1+offset;
    
    tjc's avatar
    tjc committed
        int thisEnd   = thisStart + samRecord.getReadString().length();
        
    
        if(ypos <= 0)
        {
          offTheTop = true;