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