From b0c7373e59297d8a226ba3497bb3cf365df27e8b Mon Sep 17 00:00:00 2001
From: tjc <tjc@ee4ac58c-ac51-4696-9907-e4b3aa274f04>
Date: Tue, 9 Mar 2010 12:37:32 +0000
Subject: [PATCH] add SNP graph

git-svn-id: svn+ssh://svn.internal.sanger.ac.uk/repos/svn/pathsoft/artemis/trunk@13445 ee4ac58c-ac51-4696-9907-e4b3aa274f04
---
 .../artemis/components/alignment/BamView.java |  53 +++-
 .../components/alignment/SnpPanel.java        | 288 ++++++++++++++++++
 2 files changed, 335 insertions(+), 6 deletions(-)
 create mode 100644 uk/ac/sanger/artemis/components/alignment/SnpPanel.java

diff --git a/uk/ac/sanger/artemis/components/alignment/BamView.java b/uk/ac/sanger/artemis/components/alignment/BamView.java
index 77b1be334..a65f8e95a 100644
--- a/uk/ac/sanger/artemis/components/alignment/BamView.java
+++ b/uk/ac/sanger/artemis/components/alignment/BamView.java
@@ -149,11 +149,13 @@ public class BamView extends JPanel
   private boolean isPairedStackView = false;
   private boolean isStrandStackView = false;
   private boolean isCoverage = false;
+  private boolean isSNPplot = false;
   
   private FeatureDisplay feature_display;
   private Selection selection;
   private JPanel mainPanel;
   private CoveragePanel coveragePanel;
+  private SnpPanel snpPanel;
   private boolean showScale = true;
   private boolean logScale = false;
   private Ruler ruler;
@@ -665,6 +667,12 @@ public class BamView extends JPanel
 	    coveragePanel.setPixPerBase(pixPerBase);
 	    coveragePanel.repaint();
 	  }
+	  if(isSNPplot)
+	  {
+	    snpPanel.setStartAndEnd(start, end);
+	    snpPanel.setPixPerBase(pixPerBase);
+	    snpPanel.repaint();
+	  }
 	}
 
 	if(waitingFrame.isVisible())
@@ -1892,6 +1900,10 @@ public class BamView extends JPanel
     coveragePanel = new CoveragePanel(this);
     bottomPanel.add(coveragePanel, BorderLayout.CENTER);
 
+    //
+    snpPanel = new SnpPanel(this, bases);
+    bottomPanel.add(snpPanel, BorderLayout.NORTH);
+    
     if(feature_display == null)
     {
       scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1,
@@ -1902,6 +1914,10 @@ public class BamView extends JPanel
         public void adjustmentValueChanged(AdjustmentEvent e)
         {
           repaint();
+          if(coveragePanel != null)
+            coveragePanel.repaint();
+          if(snpPanel != null)
+            snpPanel.repaint();
         }
       });
       bottomPanel.add(scrollBar, BorderLayout.SOUTH);
@@ -1910,7 +1926,9 @@ public class BamView extends JPanel
     mainPanel.add(bottomPanel, BorderLayout.SOUTH);
     coveragePanel.setPreferredSize(new Dimension(900, 100));
     coveragePanel.setVisible(false);
-    
+    snpPanel.setPreferredSize(new Dimension(900, 100));
+    snpPanel.setVisible(false);
+
     jspView.getVerticalScrollBar().setValue(
         jspView.getVerticalScrollBar().getMaximum());
     jspView.getVerticalScrollBar().setUnitIncrement(maxUnitIncrement);
@@ -2096,7 +2114,7 @@ public class BamView extends JPanel
     viewMenu.add(checkBoxStrandStackView);  
     menu.add(viewMenu);
  
-    final JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNPs");
+    final JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNP marks");
     // 
     JMenu colourMenu = new JMenu("Colour By");
     colourMenu.add(colourByCoverageColour);
@@ -2156,8 +2174,10 @@ public class BamView extends JPanel
       }
     });
     showMenu.add(markInsertions);
-    showMenu.add(new JSeparator());
+    menu.add(showMenu);
     
+    //
+    JMenu graphMenu = new JMenu("Graph");
     JCheckBoxMenuItem checkBoxCoverage = new JCheckBoxMenuItem("Coverage");
     checkBoxCoverage.addActionListener(new ActionListener()
     {
@@ -2168,8 +2188,21 @@ public class BamView extends JPanel
         repaint();
       }
     });
-    showMenu.add(checkBoxCoverage);
-    menu.add(showMenu);
+    graphMenu.add(checkBoxCoverage);
+    
+    JCheckBoxMenuItem checkBoxSNP = new JCheckBoxMenuItem("SNP");
+    checkBoxSNP.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        isSNPplot = !isSNPplot;
+        snpPanel.setVisible(isSNPplot);
+        repaint();
+      }
+    });
+    graphMenu.add(checkBoxSNP);
+    menu.add(graphMenu);
+    
     
     if(feature_display != null)
     {
@@ -2250,7 +2283,7 @@ public class BamView extends JPanel
     jspView.getViewport().setViewPosition(p);
   }
   
-  private int getBaseAtStartOfView()
+  protected int getBaseAtStartOfView()
   {
     if(feature_display != null)
       return feature_display.getForwardBaseAtLeftEdge();
@@ -2581,12 +2614,20 @@ public class BamView extends JPanel
       int width = feature_display.getMaxVisibleBases();
       setZoomLevel(width);
       repaint();
+      if(coveragePanel != null && coveragePanel.isVisible())
+        coveragePanel.repaint();
+      if(snpPanel != null && snpPanel.isVisible())
+        snpPanel.repaint();
     }
     else
     {
       setDisplay(event.getStart(), 
         event.getStart()+feature_display.getMaxVisibleBases(), event);
       repaint();
+      if(coveragePanel != null && coveragePanel.isVisible())
+        coveragePanel.repaint();
+      if(snpPanel != null && snpPanel.isVisible())
+        snpPanel.repaint();
     }
   }
   
diff --git a/uk/ac/sanger/artemis/components/alignment/SnpPanel.java b/uk/ac/sanger/artemis/components/alignment/SnpPanel.java
new file mode 100644
index 000000000..2463665ea
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SnpPanel.java
@@ -0,0 +1,288 @@
+/* SnpPanel
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010  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.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.GeneralPath;
+import java.util.List;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import net.sf.samtools.AlignmentBlock;
+import net.sf.samtools.SAMRecord;
+
+  public class SnpPanel extends JPanel
+  {
+    private static final long serialVersionUID = 1L;
+
+    private int start;
+    private int end;
+    private float pixPerBase;
+    private BamView bamView;
+    private JPopupMenu popup;
+    private Bases bases;
+    private float minBaseQualityFilter = 0;
+    
+    public SnpPanel(final BamView bamView, Bases bases)
+    {
+      super();
+      setBackground(Color.white);
+      this.bamView = bamView;
+      this.bases = bases;
+      
+      popup = new JPopupMenu();
+      JMenuItem configure = new JMenuItem("Filter by Base Quality...");
+      configure.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent e)
+        {
+          // filter by base quality
+          JTextField filterField = new JTextField(Float.toString(minBaseQualityFilter));
+
+          int status = JOptionPane.showConfirmDialog(SnpPanel.this, 
+              filterField, "Base Quality Filter", 
+              JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+          if(status == JOptionPane.CANCEL_OPTION)
+            return;
+          try
+          {
+            minBaseQualityFilter = Float.parseFloat(filterField.getText());
+          }
+          catch(NumberFormatException nfe)
+          {
+            JOptionPane.showMessageDialog(SnpPanel.this, nfe.getMessage(), 
+                "Number Format", JOptionPane.WARNING_MESSAGE);
+          }
+        }
+      });
+      popup.add(configure);
+      addMouseListener(new PopupListener());
+    }
+    
+    /**
+     * Override
+     */
+    protected void paintComponent(Graphics g)
+    {
+      super.paintComponent(g);
+      Graphics2D g2 = (Graphics2D)g;
+      
+      if(bases == null)
+        return;
+      
+      List<SAMRecord> readsInView = bamView.getReadsInView();
+      if(readsInView == null)
+        return;
+      
+      int windowSize = (bamView.getBasesInView()/300);
+      if(windowSize < 1)
+        windowSize = 1;
+
+      int nBins = Math.round((end-start+1.f)/windowSize);
+      int max = drawPlot(g2, nBins, windowSize);
+      
+      String maxStr = Float.toString(max/windowSize);
+      FontMetrics fm = getFontMetrics(getFont());
+      g2.setColor(Color.black);
+      
+      int xpos = getWidth() - fm.stringWidth(maxStr) - 
+      bamView.getJspView().getVerticalScrollBar().getWidth();
+      g2.drawString(maxStr, xpos, fm.getHeight());
+    }
+    
+    
+    private int drawPlot(Graphics2D g2, int nBins, int windowSize)
+    {
+      //lines = CoveragePanel.getLineAttributes(bamView.bamList.size());
+      List<SAMRecord> readsInView = bamView.getReadsInView();
+      
+      int snpCount[] = new int[nBins];
+      for(int i=0; i<snpCount.length; i++)
+        snpCount[i] = 0;
+      
+      int max = 0;
+      for(int i=0; i<readsInView.size(); i++)
+      {
+        SAMRecord thisRead = readsInView.get(i);
+        max = calculateSNPs(thisRead, windowSize, nBins, snpCount, max);
+      }
+
+      g2.setColor(Color.red);
+      g2.setStroke(new BasicStroke(1.f));
+      
+      if(windowSize == 1)
+      {
+        GeneralPath shape = new GeneralPath();
+        shape.moveTo(0,getHeight());
+        for(int i=0; i<snpCount.length; i++)
+        {
+          float xpos1 = ((i*windowSize) )*pixPerBase;
+          float xpos2 = ((i*windowSize) + windowSize)*pixPerBase;
+          
+          shape.lineTo(xpos1,getHeight());
+          shape.lineTo(xpos1,
+              getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+          shape.lineTo(xpos2,
+              getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+          
+          shape.lineTo(xpos2,getHeight());
+        }
+
+        shape.lineTo(getWidth(),getHeight());
+        g2.fill(shape);
+      }
+      else
+      {
+        for(int i=1; i<snpCount.length; i++)
+        {
+          int x0 = (int) (((i*windowSize) - windowSize/2.f)*pixPerBase);
+          int y0 = (int) (getHeight() - (((float)snpCount[i-1]/(float)max)*getHeight()));
+          int x1 = (int) ((((i+1)*windowSize) - windowSize/2.f)*pixPerBase);
+          int y1 = (int) (getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+        
+          g2.drawLine(x0, y0, x1, y1);
+        }
+      }
+
+      return max;
+    }
+    
+    /**
+     * Display the SNPs for the given read.
+     * @param g2
+     * @param thisRead
+     * @param pixPerBase
+     * @param ypos
+     */
+    private int calculateSNPs(SAMRecord thisRead,
+                               int windowSize, 
+                               int nBins,
+                               int[] snpCount,
+                               int max)
+    {
+      int thisStart = thisRead.getAlignmentStart();
+      int thisEnd   = thisRead.getAlignmentEnd();
+      int offset    = bamView.getSequenceOffset(thisRead.getReferenceName());
+      // use alignment blocks of the contiguous alignment of
+      // subsets of read bases to a reference sequence
+      List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
+      byte[] phredQuality = thisRead.getBaseQualities();
+      try
+      {
+        char[] refSeq = bases.getSubSequenceC(
+            new Range(thisStart+offset, thisEnd+offset), Bases.FORWARD);
+        byte[] readSeq = thisRead.getReadBases();
+
+        offset = offset - bamView.getBaseAtStartOfView();
+        for(int i=0; i<blocks.size(); i++)
+        {
+          AlignmentBlock block = blocks.get(i);
+          for(int j=0; j<block.getLength(); j++)
+          {
+            int readPos = block.getReadStart()-1+j;
+            int refPos  = block.getReferenceStart()+j;
+
+            if (Character.toUpperCase(refSeq[refPos-thisStart]) != readSeq[readPos])
+            {
+              if(phredQuality[readPos] < minBaseQualityFilter)
+                continue;
+              int bin = (int)((refPos+offset) / windowSize);
+
+              if(bin < 0 || bin > nBins-1)
+                continue;
+
+              snpCount[bin]+=1;
+              if(snpCount[bin] > max)
+                max = snpCount[bin];
+            }
+          }
+        }
+      }
+      catch (OutOfRangeException e)
+      {
+        e.printStackTrace();
+      }
+      return max;
+    }
+
+    protected void setStartAndEnd(int start, int end)
+    {
+      this.start = start;
+      this.end = end;
+    }
+
+    protected void setPixPerBase(float pixPerBase)
+    {
+      this.pixPerBase = pixPerBase;
+    }
+
+  /**
+   * Popup menu listener
+   */
+   class PopupListener extends MouseAdapter
+   {
+     JMenuItem gotoMateMenuItem;
+     JMenuItem showDetails;
+     
+     public void mouseClicked(MouseEvent e)
+     {
+     }
+     
+     public void mousePressed(MouseEvent e)
+     {
+       maybeShowPopup(e);
+     }
+
+     public void mouseReleased(MouseEvent e)
+     {
+       maybeShowPopup(e);
+     }
+
+     private void maybeShowPopup(MouseEvent e)
+     {
+       if(e.isPopupTrigger())
+       {
+         popup.show(e.getComponent(),
+                 e.getX(), e.getY());
+       }
+     }
+   }
+  }
\ No newline at end of file
-- 
GitLab