/* VCFview * * created: July 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.variant; import java.awt.AlphaComposite; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.regex.Pattern; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import htsjdk.samtools.util.BlockCompressedInputStream; import org.apache.log4j.Level; import uk.ac.sanger.artemis.Entry; import uk.ac.sanger.artemis.EntryGroup; import uk.ac.sanger.artemis.Feature; import uk.ac.sanger.artemis.FeatureKeyPredicate; import uk.ac.sanger.artemis.FeatureVector; import uk.ac.sanger.artemis.Options; import uk.ac.sanger.artemis.Selection; import uk.ac.sanger.artemis.SelectionChangeEvent; import uk.ac.sanger.artemis.SelectionChangeListener; import uk.ac.sanger.artemis.SimpleEntryGroup; import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent; import uk.ac.sanger.artemis.components.DisplayAdjustmentListener; import uk.ac.sanger.artemis.components.EntryEdit; import uk.ac.sanger.artemis.components.EntryFileDialog; import uk.ac.sanger.artemis.components.FeatureDisplay; import uk.ac.sanger.artemis.components.FileViewer; import uk.ac.sanger.artemis.components.IndexReferenceEvent; import uk.ac.sanger.artemis.components.MessageDialog; import uk.ac.sanger.artemis.components.MultiComparator; import uk.ac.sanger.artemis.components.SequenceComboBox; import uk.ac.sanger.artemis.components.Utilities; import uk.ac.sanger.artemis.components.alignment.FileSelectionDialog; import uk.ac.sanger.artemis.components.alignment.LineAttributes; import uk.ac.sanger.artemis.editor.MultiLineToolTipUI; import uk.ac.sanger.artemis.io.EntryInformation; import uk.ac.sanger.artemis.io.Key; import uk.ac.sanger.artemis.io.Range; import uk.ac.sanger.artemis.io.RangeVector; import uk.ac.sanger.artemis.sequence.Bases; import uk.ac.sanger.artemis.sequence.MarkerRange; 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.FTPSeekableStream; import uk.ac.sanger.artemis.util.OutOfRangeException; public class VCFview extends JPanel implements DisplayAdjustmentListener, SelectionChangeListener { private static final long serialVersionUID = 1L; private JScrollPane jspView; private JScrollBar scrollBar; private JPanel vcfPanel; private AbstractVCFReader vcfReaders[]; private List vcfFiles; private List hideVcfList = new Vector(); private FeatureDisplay feature_display; private Selection selection; private int nbasesInView; protected int seqLength; private EntryGroup entryGroup; private String chr; private Point lastMousePoint; private VCFRecord mouseVCF; private int mouseOverIndex = -1; private int mouseOverSampleIndex = -1; private GraphPanel graphPanel; //record of where a mouse drag starts private int dragStart = -1; private JPopupMenu popup; private JMenu vcfFilesMenu = new JMenu("VCF files"); private int LINE_HEIGHT = 14; protected boolean showSynonymous = true; protected boolean showNonSynonymous = true; protected boolean showDeletions = true; protected boolean showInsertions = true; protected boolean showMultiAlleles = true; protected boolean showHomozygous = true; // show variants that do not overlap CDS protected boolean showNonOverlappings = true; protected boolean showNonVariants = false; private Map manualHash = new HashMap(); //private boolean markAsNewStop = false; private boolean showLabels = false; private JCheckBoxMenuItem markNewStops = new JCheckBoxMenuItem("Mark new stops within CDS features", true); private static int VARIANT_COLOUR_SCHEME = 0; private static int SYN_COLOUR_SCHEME = 1; private static int QUAL_COLOUR_SCHEME = 2; private int colourScheme = 0; private Color colMap[] = makeColours(Color.RED, 255); private Color lighterGrey = new Color(220,220,220); private VCFFilter filter; Hashtable offsetLengths = null; private boolean concatSequences = false; private boolean splitSamples = true; protected static Pattern tabPattern = Pattern.compile("\t"); public static String VCFFILE_SUFFIX = ".*\\.[bv]{1}cf(\\.gz)*$"; private static String FILE_SUFFIX = "\\.[bv]{1}cf(\\.gz)*$"; private List cacheVariantLines; private SequenceComboBox combo; public static org.apache.log4j.Logger logger4j = org.apache.log4j.Logger.getLogger(VCFview.class); public VCFview(final JFrame frame, final JPanel vcfPanel, final List vcfFiles, final int nbasesInView, final int seqLength, final String chr, final String reference, final EntryEdit entry_edit, final FeatureDisplay feature_display) { super(); this.nbasesInView = nbasesInView; this.seqLength = seqLength; this.chr = chr; this.feature_display = feature_display; this.vcfPanel = vcfPanel; this.vcfFiles = vcfFiles; jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); setBackground(Color.white); MultiLineToolTipUI.initialize(); setToolTipText(""); vcfPanel.setPreferredSize(new Dimension(900, (vcfFiles.size()+1)*(LINE_HEIGHT+5))); if(feature_display != null) this.entryGroup = feature_display.getEntryGroup(); else if(reference != null) this.entryGroup = getReference(reference); if(entryGroup != null) this.seqLength = entryGroup.getSequenceEntry().getBases().getLength(); try { vcfReaders = new AbstractVCFReader[vcfFiles.size()]; for(int i=0; i this.seqLength) this.nbasesInView = this.seqLength/2; scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, this.nbasesInView, 1, this.seqLength); scrollBar.setUnitIncrement(nbasesInView/20); scrollBar.addAdjustmentListener(new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { repaint(); } }); // // addMouseListener(new PopupListener()); // createTopPanel(frame, entry_edit); createMenus(); setDisplay(); if(feature_display == null) { bottomPanel.add(scrollBar, BorderLayout.SOUTH); selection = new Selection(null); } else { Border empty = new EmptyBorder(0,0,0,0); jspView.setBorder(empty); selection = feature_display.getSelection(); } } private void createMenus() { // popup menu popup = new JPopupMenu(); JMenuItem addVCFMenu = new JMenuItem("Add VCF ..."); addVCFMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { FileSelectionDialog fileSelection = new FileSelectionDialog( null, true, "VCFview", "VCF"); List vcfFileList = fileSelection.getFiles(VCFFILE_SUFFIX); vcfFiles.addAll(vcfFileList); int count = vcfFileList.size(); int oldSize = vcfReaders.length; AbstractVCFReader[] trTmp = new AbstractVCFReader[count + vcfReaders.length]; System.arraycopy(vcfReaders, 0, trTmp, 0, vcfReaders.length); vcfReaders = trTmp; for (int i = 0; i < vcfFileList.size(); i++) readHeader(vcfFileList.get(i), i+oldSize); for(int i=0; i 1) combo.addItem("Combine References"); if(chr == null) this.chr = vcfReaders[0].getSeqNames()[0]; combo.setSelectedItem(this.chr); topPanel.add(combo); if(topPanel instanceof JPanel) vcfPanel.add(topPanel, BorderLayout.NORTH); // auto hide top panel final JCheckBox buttonAutoHide = new JCheckBox("Hide", true); buttonAutoHide.setToolTipText("Auto-Hide"); topPanel.add(buttonAutoHide); final MouseMotionListener mouseMotionListener = new MouseMotionListener() { public void mouseDragged(MouseEvent event) { handleCanvasMouseDrag(event); } public void mouseMoved(MouseEvent e) { lastMousePoint = e.getPoint(); int thisHgt = HEIGHT; if (thisHgt < 5) thisHgt = 15; int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY()); if (y < thisHgt) topPanel.setVisible(true); else { if (buttonAutoHide.isSelected()) topPanel.setVisible(false); } } }; addMouseMotionListener(mouseMotionListener); if(feature_display != null) { JButton close = new JButton("Close"); topPanel.add(close); close.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); if(entry_edit != null) entry_edit.setNGDivider(); else vcfPanel.setVisible(false); } }); } } /** * Create an icon of a box using the given colour. * @param c * @return */ protected ImageIcon getImageIcon(Color c) { BufferedImage image = (BufferedImage)this.createImage(10, 10); if(image == null) return null; Graphics2D g2 = image.createGraphics(); g2.setColor(c); g2.fillRect(0, 0, 10, 10); return new ImageIcon(image); } private void addToViewMenu(final int thisBamIndex) { LineAttributes ln[] = GraphPanel.getLineAttributes(vcfReaders.length); final JCheckBoxMenuItem cbBam = new JCheckBoxMenuItem( getLabel(thisBamIndex), getImageIcon(ln[thisBamIndex].getLineColour()), true); vcfFilesMenu.add(cbBam); cbBam.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(cbBam.isSelected()) hideVcfList.remove(new Integer(thisBamIndex)); else hideVcfList.add(new Integer(thisBamIndex)); repaint(); } }); } /** * Test and download if on a http server * @param fileName * @return */ private String testForURL(String fileName, boolean isBCF) { if(!fileName.startsWith("http:") && !fileName.startsWith("ftp:")) return fileName; return download(fileName+".tbi", ".tbi"); } protected String download(String f, String suffix) { try { final URL urlFile = new URL(f); InputStream is = urlFile.openStream(); // Create temp file. String name = urlFile.getFile(); int ind = name.lastIndexOf('/'); if(ind > -1) name = name.substring(ind+1); File bcfFile = File.createTempFile(name.replaceAll("[\\/\\s]", "_"), suffix); bcfFile.deleteOnExit(); FileOutputStream out = new FileOutputStream(bcfFile); int c; while ((c = is.read()) != -1) out.write(c); out.flush(); out.close(); is.close(); return bcfFile.getAbsolutePath(); } catch(IOException ioe) { JOptionPane.showMessageDialog(null, "Problem downloading\n"+f, "Problem", JOptionPane.WARNING_MESSAGE); } return null; } private static EntryGroup getReference(String reference) { EntryGroup entryGroup = new SimpleEntryGroup(); final Document entry_document = DocumentFactory.makeDocument(reference); final EntryInformation artemis_entry_information = Options.getArtemisEntryInformation(); final uk.ac.sanger.artemis.io.Entry new_embl_entry = EntryFileDialog.getEntryFromFile(null, entry_document, artemis_entry_information, false); if(new_embl_entry != null) // the read failed { Entry entry = null; Bases bases = null; try { if (entryGroup.getSequenceEntry() != null) bases = entryGroup.getSequenceEntry().getBases(); if (bases == null) { entry = new Entry(new_embl_entry); bases = entry.getBases(); } else entry = new Entry(bases, new_embl_entry); entryGroup.add(entry); } catch (OutOfRangeException e) { new MessageDialog(null, "read failed: one of the features in " + reference + " has an out of range " + "location: " + e.getMessage()); } catch (NoSequenceException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return entryGroup; } /** * Read the vcf header * @param fileName * @return */ private void readHeader(String fileName, int index) { StringBuffer buff = new StringBuffer(); //buff.append(fileName+"\n"); try { if(IOUtils.isBCF(fileName)) { vcfReaders[index] = new BCFReader(fileName); String hdr = ((BCFReader)vcfReaders[index]).headerToString(); if(hdr.indexOf("VCFv4") > -1) vcfReaders[index].setVcf_v4(true); vcfReaders[index].setHeader(hdr); return; } String indexfileName = testForURL(fileName, false); BlockCompressedInputStream is; if(fileName.startsWith("http")|| fileName.startsWith("ftp")) { URL url = new URL(fileName); if(fileName.startsWith("ftp")) vcfReaders[index] = new TabixReader(indexfileName.substring(0, indexfileName.length()-4), new FTPSeekableStream(url)); else vcfReaders[index] = new TabixReader(indexfileName.substring(0, indexfileName.length()-4), url); is = new BlockCompressedInputStream(url); } else { vcfReaders[index] = new TabixReader(fileName); is = new BlockCompressedInputStream(new FileInputStream(fileName)); } String line; while( (line = TabixReader.readLine(is) ) != null ) { if(!line.startsWith("#")) break; if(line.indexOf("VCFv4") > -1) vcfReaders[index].setVcf_v4(true); buff.append(line+"\n"); } is.close(); } catch (IOException e) { e.printStackTrace(); } vcfReaders[index].setHeader(buff.toString()); return; } /** * Set the number of bases being displayed * @param nbasesInView */ private void setZoomLevel(final int nbasesInView) { int startValue = scrollBar.getValue(); this.nbasesInView = nbasesInView; float pixPerBase = getPixPerBaseByWidth(); this.nbasesInView = (int)(getWidth()/pixPerBase); if(scrollBar != null) { scrollBar.setValues(startValue, nbasesInView, 1, seqLength); scrollBar.setUnitIncrement(nbasesInView/20); scrollBar.setBlockIncrement(nbasesInView); } } public String getToolTipText() { if(vcfReaders == null) return null; mouseVCF = null; findVariantAtPoint(lastMousePoint); if(mouseVCF == null) return null; String msg = "Seq: "+mouseVCF.getChrom()+"\n"; msg += "Pos: "+mouseVCF.getPos()+"\n"; msg += "ID: "+mouseVCF.getID()+"\n"; msg += "Variant: "+mouseVCF.getRef()+" -> "+mouseVCF.getAlt().toString()+"\n"; msg += "Qual: "+mouseVCF.getQuality()+"\n"; String dp; if(splitSamples && mouseOverSampleIndex >= 0) { msg += "Genotype "; msg += mouseVCF.getFormat(); msg += "\n"; msg += mouseVCF.getFormatValueForSample(mouseOverSampleIndex); } else if((dp = mouseVCF.getInfoValue("DP")) != null) { msg += "DP:"+dp; } return msg; } /** * For VCF files with multiple references sequences, calculate * the offset from the start of the concatenated sequence for * a given reference. * @param refName * @return */ protected int getSequenceOffset(String refName) { if(!concatSequences) return 0; if(offsetLengths == null) { String[] contigs = vcfReaders[0].getSeqNames(); FeatureVector features = entryGroup.getAllFeatures(); offsetLengths = new Hashtable(contigs.length); for(int i=0; i= start && offset < end) || (offset < start && start < nextOffset) ) { int thisStart = start - offset; if(thisStart < 1) thisStart = 1; int thisEnd = end - offset; drawRegion(g2d, contigs[j], thisStart, thisEnd, i, sumSamples, start, pixPerBase, features); } } } else { int thisStart = start; if(thisStart < 1) thisStart = 1; drawRegion(g2d, chr, thisStart, end, i, sumSamples, start, pixPerBase, features); } sumSamples += vcfReaders[i].getNumberOfSamples(); } if(feature_display == null) drawScale(g2d, start, end, pixPerBase, getHeight()); // show labels for each VCF if(showLabels) showLabels(g2d); } private void showLabels(Graphics2D g2d) { int max = 0; final FontMetrics fm = getFontMetrics(getFont()); for (int i = 0; i < vcfReaders.length; i++) { if(hideVcfList.contains(i)) continue; if(splitSamples) { for(int sampleIdx = 0; sampleIdx < vcfReaders[i].getNumberOfSamples(); sampleIdx++) { if(vcfReaders[i].sampleNames == null) { int width = fm.stringWidth(getLabel(i)); if (max < width) max = width; break; } String labStr = vcfReaders[i].sampleNames[sampleIdx]; int width = fm.stringWidth(labStr); if (max < width) max = width; } } else { String labStr = getLabel(i); int width = fm.stringWidth(labStr); if (max < width) max = width; } } Rectangle square = new Rectangle(0, 0, max, getHeight()); Composite originalComposite = g2d.getComposite(); g2d.setPaint(Color.lightGray); g2d.setComposite(makeComposite(0.75f)); g2d.fill(square); g2d.setComposite(originalComposite); g2d.setColor(Color.black); g2d.drawLine(max+1, 0, max+1, getHeight()); int sumSample = 0; for (int i = 0; i < vcfReaders.length; i++) { if(hideVcfList.contains(i)) continue; if(splitSamples) { for(int sampleIdx=0; sampleIdx < vcfReaders[i].getNumberOfSamples(); sampleIdx++) { if(vcfReaders[i].sampleNames == null) g2d.drawString(getLabel(i), 1, getYPostion(sumSample+sampleIdx)); else g2d.drawString(vcfReaders[i].sampleNames[sampleIdx], 1, getYPostion(sumSample+sampleIdx)); } sumSample += vcfReaders[i].getNumberOfSamples(); } else g2d.drawString(getLabel(i), 1, getYPostion(i)); } } private String getLabel(int index) { return vcfReaders[index].getName().replaceAll(FILE_SUFFIX, ""); } private AlphaComposite makeComposite(float alpha) { return(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); } private void drawRegion(final Graphics2D g, final String chr, final int sbeg, final int send, final int vcfFileIndex, final int sumSamples, final int start, final float pixPerBase, final FeatureVector features) { cacheVariantLines = new Vector(5); try { VCFRecord record; // viewport position and height int viewIndex = getHeight()/(LINE_HEIGHT+5) - jspView.getViewport().getViewPosition().y/(LINE_HEIGHT+5); int viewHgt = jspView.getViewport().getExtentSize().height/(LINE_HEIGHT+5); while((record = vcfReaders[vcfFileIndex].getNextRecord(chr, sbeg, send)) != null) { int basePosition = record.getPos() + getSequenceOffset(record.getChrom()); if(!splitSamples) { drawVariantCall(g, record, start, vcfFileIndex, -1, -1, pixPerBase, features, vcfReaders[vcfFileIndex], basePosition); continue; } for(int sampleIndex = 0; sampleIndex < vcfReaders[vcfFileIndex].getNumberOfSamples(); sampleIndex++) { if(sampleIndex+sumSamples <= viewIndex+2 && sampleIndex+sumSamples >= viewIndex-viewHgt-2) { drawVariantCall(g, record, start, vcfFileIndex, sampleIndex, sumSamples, pixPerBase, features, vcfReaders[vcfFileIndex], basePosition); } } } } catch (IOException e) { logger4j.warn(chr+":"+sbeg+"-"+send+"\n"+e.getMessage()); e.printStackTrace(); } } protected FeatureVector getCDSFeaturesInRange(int start, int end) { if(entryGroup == null) return null; try { Range range = new Range(start, end); FeatureVector features = entryGroup.getFeaturesInRange(range); FeatureKeyPredicate predicate = new FeatureKeyPredicate(Key.CDS); final FeatureVector cdsFeatures = new FeatureVector(); for(int i=0; i rangeEnd) return; int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView())); int width = (int) (pixPerBase*(rangeEnd-rangeStart+1)); g2.setColor(Color.pink); g2.fillRect(x, 0, width, getHeight()); } } } private Selection getSelection() { return selection; } protected int getBaseAtStartOfView() { if(feature_display != null) return feature_display.getForwardBaseAtLeftEdge(); else return scrollBar.getValue(); } protected boolean showVariant(final VCFRecord record, final FeatureVector features, final int basePosition, final AbstractVCFReader vcfReader, final int nsample, final int vcfIndex) { return VCFFilter.passFilter(manualHash, record, vcfReader, features, basePosition, nsample, vcfIndex); } private void setAsStop(VCFRecord record, FeatureVector features, int basePosition, AbstractVCFReader vcfReader) { if(!record.isMarkAsNewStop() && markNewStops.isSelected() && !record.getAlt().isDeletion(vcfReader.isVcf_v4()) && !record.getAlt().isInsertion(vcfReader.isVcf_v4()) && record.getAlt().length() == 1 && record.getRef().length() == 1) { short isSyn = record.getSynFlag(features, basePosition); if(isSyn == 2) record.setMarkAsNewStop(true); } } protected static boolean isOverlappingFeature(FeatureVector features, int basePosition) { for(int i = 0; i basePosition) { RangeVector ranges = feature.getLocation().getRanges(); for(int j=0; j< ranges.size(); j++) { Range range = (Range) ranges.get(j); if(range.getStart() < basePosition && range.getEnd() > basePosition) return true; } } } return false; } protected static boolean isOverlappingFeature(List cdsFeatures, int basePosition) { for (CDSFeature cdsFeature : cdsFeatures) { if (cdsFeature.firstBase < basePosition && cdsFeature.lastBase > basePosition) { for(int i = 0 ; i < cdsFeature.ranges.size() ; ++i) { Range range = (Range)cdsFeature.ranges.elementAt(i); if (range.getStart() < basePosition && range.getEnd() > basePosition) return true; } } } return false; } /** * Draw the VCF record * @param g * @param record * @param start * @param vcfIndex * @param sampleIndex * @param sumSamples * @param pixPerBase * @param features * @param vcfReader * @param basePosition */ private void drawVariantCall(final Graphics2D g, final VCFRecord record, final int start, final int vcfIndex, final int sampleIndex, final int sumSamples, final float pixPerBase, final FeatureVector features, final AbstractVCFReader vcfReader, final int basePosition) { boolean show = showVariant(record, features, basePosition, vcfReader, sampleIndex, vcfIndex); if( !show ) return; setAsStop(record, features, basePosition, vcfReader); final int pos[]; if(sampleIndex < 0) pos = getScreenPosition(basePosition, pixPerBase, start, vcfIndex); else pos = getScreenPosition(basePosition, pixPerBase, start, sampleIndex+sumSamples); if (colourScheme == QUAL_COLOUR_SCHEME) g.setColor(getQualityColour(record)); else if (record.getAlt().isDeletion(vcfReader.isVcf_v4())) g.setColor(Color.gray); else if (record.getAlt().isInsertion(vcfReader.isVcf_v4())) g.setColor(Color.magenta); else if (record.getAlt().isMultiAllele(sampleIndex)) { g.setColor(Color.orange); g.fillArc(pos[0] - 3, pos[1] - LINE_HEIGHT - 3, 6, 6, 0, 360); } else if (record.getAlt().length() == 1 && record.getRef().length() == 1) { g.setColor(getColourForSNP(record, features, basePosition)); if (record.getAlt().isNonVariant()) { // use the cache to avoid drawing over a variant with a non-variant if (!cacheVariantLines.contains(pos[0])) g.drawLine(pos[0], pos[1], pos[0], pos[1] - LINE_HEIGHT + 6); return; } } else g.setColor(Color.pink); if (record.isMarkAsNewStop()) g.fillArc(pos[0] - 3, pos[1] - (LINE_HEIGHT / 2) - 3, 6, 6, 0, 360); if (cacheVariantLines.size() == 5) cacheVariantLines.clear(); cacheVariantLines.add(pos[0]); g.drawLine(pos[0], pos[1], pos[0], pos[1] - LINE_HEIGHT); } /** * Determine the colour depending on the colour scheme in use. * @param record * @param features * @param basePosition * @return */ private Color getColourForSNP(VCFRecord record, FeatureVector features, int basePosition) { if(colourScheme == VARIANT_COLOUR_SCHEME) return getVariantColour(record.getAlt().toString()); else if(colourScheme == SYN_COLOUR_SCHEME) // synonymous / non-synonymous { if(!record.getAlt().isNonVariant()) { short synFlag = record.getSynFlag(features, basePosition); if(synFlag == 1) return Color.red; else if(synFlag == 0 || synFlag == 2) return Color.blue; } return getVariantColour(record.getAlt().toString()); } else // score return getQualityColour(record); } private Color getQualityColour(VCFRecord record) { if(colMap == null) colMap = makeColours(Color.RED, 255); int idx = (int) record.getQuality()-1; if(idx > colMap.length-1) idx = colMap.length-1; else if(idx < 0) idx = 0; return colMap[idx]; } private Color getVariantColour(String variant) { if(variant.equals("C")) return Color.red; else if(variant.equals("A")) return Color.green; else if(variant.equals("G")) return Color.blue; else if(variant.equals("T")) return Color.black; else return lighterGrey; // non-variant } /** * Generate the colours for heat map plots. * @param col * @param NUMBER_OF_SHADES * @return */ private Color[] makeColours(Color col, int NUMBER_OF_SHADES) { Color definedColour[] = new Color[NUMBER_OF_SHADES]; for(int i = 0; i < NUMBER_OF_SHADES; ++i) { int R = col.getRed(); int G = col.getGreen(); int B = col.getBlue(); float scale = ((float)(NUMBER_OF_SHADES-i) * (float)(255 / NUMBER_OF_SHADES )) ; if((R+scale) <= 255) R += scale; else R = 254; if((G+scale) <= 255) G += scale; else G = 254; if((B+scale) <= 255) B += scale; else B = 254; definedColour[i] = new Color(R,G,B); } return definedColour; } private int[] getScreenPosition(int base, float pixPerBase, int start, int vcfFileIndex) { int pos[] = new int[2]; pos[0] = Math.round((base - start)*pixPerBase); pos[1] = getYPostion(vcfFileIndex); return pos; } private int getYPostion(int vcfFileIndex) { int pos = 0; if(hideVcfList.size() == 0 || splitSamples) pos = vcfFileIndex; else { for(int i=0; i= start && offset < end) || (offset < start && start < nextOffset) ) { int thisStart = start - offset; if(thisStart < 1) thisStart = 1; int thisEnd = end - offset; searchRegion(contigs[k], thisStart, thisEnd, i, sumSamples, mousePoint, features, startBase, pixPerBase); } } } else { int thisStart = start; if(thisStart < 1) thisStart = 1; searchRegion(chr, thisStart, end, i, sumSamples, mousePoint, features, startBase, pixPerBase); } sumSamples += vcfReaders[i].getNumberOfSamples(); } } private void searchRegion(final String chr, final int sbeg, final int send, final int fileIndex, final int sumSamples, final Point mousePoint, FeatureVector features, int start, float pixPerBase) { try { VCFRecord bcfRecord; while((bcfRecord = vcfReaders[fileIndex].getNextRecord(chr, sbeg, send)) != null) { if(splitSamples) { for(int sampleIndex=0; sampleIndex ypos && mousePoint.getY() < ypos-LINE_HEIGHT) continue; isMouseOver(mousePoint, bcfRecord, features, fileIndex, sampleIndex, sumSamples, start, pixPerBase, vcfReaders[fileIndex]); } } else { int ypos = getYPostion(fileIndex); if(mousePoint.getY() > ypos && mousePoint.getY() < ypos-LINE_HEIGHT) continue; isMouseOver(mousePoint, bcfRecord, features, fileIndex, -1, sumSamples, start, pixPerBase, vcfReaders[fileIndex]); } } } catch (IOException e) { logger4j.warn(chr+":"+sbeg+"-"+send+"\n"+e.getMessage()); e.printStackTrace(); } } private void isMouseOver(final Point mousePoint, final VCFRecord record, final FeatureVector features, final int vcfFileIndex, final int sampleIndex, final int sumSamples, final int start, final float pixPerBase, final AbstractVCFReader vcfReader) { int basePosition = record.getPos() + getSequenceOffset(record.getChrom()); if( !showVariant(record, features, basePosition, vcfReader, sampleIndex, vcfFileIndex) ) return; int pos[]; if(!splitSamples) pos = getScreenPosition(basePosition, pixPerBase, start, vcfFileIndex); else pos = getScreenPosition(basePosition, pixPerBase, start, sampleIndex+sumSamples); if(mousePoint != null && mousePoint.getY() < pos[1] && mousePoint.getY() > pos[1]-LINE_HEIGHT && mousePoint.getX() > pos[0]-3 && mousePoint.getX() < pos[0]+3) { mouseVCF = record; mouseOverIndex = vcfFileIndex; mouseOverSampleIndex = sampleIndex; } } private void drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos) { g2.setColor(Color.black); g2.drawLine( 0, ypos-14, (int)((end - start)*pixPerBase), ypos-14); int interval = end-start; if(interval > 20000000) drawTicks(g2, start, end, pixPerBase, 10000000, ypos); else if(interval > 4000000) drawTicks(g2, start, end, pixPerBase, 2000000, ypos); else if(interval > 800000) drawTicks(g2, start, end, pixPerBase, 400000, ypos); else if(interval > 160000) drawTicks(g2, start, end, pixPerBase, 80000, ypos); else if(interval > 50000) drawTicks(g2, start, end, pixPerBase, 25000, ypos); else if(interval > 16000) drawTicks(g2, start, end, pixPerBase, 8000, ypos); else if(interval > 4000) drawTicks(g2, start, end, pixPerBase, 2000, ypos); else if(interval > 1000) drawTicks(g2, start, end, pixPerBase, 500, ypos); else drawTicks(g2, start, end, pixPerBase, 100, ypos); } /** * Handle a mouse drag event on the drawing canvas. **/ private void handleCanvasMouseDrag(final MouseEvent event) { if(event.getButton() == MouseEvent.BUTTON3) return; if(event.getClickCount() > 1) { getSelection().clear(); repaint(); return; } highlightRange(event, MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK); } /** * * @param event * @param onmask */ private void highlightRange(MouseEvent event, int onmask) { if(entryGroup == null) return; float pixPerBase = getPixPerBaseByWidth(); int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() ); if(start < 1) start = 1; if(start > seqLength) start = seqLength; if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask) dragStart = start; else if((event.getModifiersEx() & onmask) != onmask) dragStart = -1; MarkerRange drag_range; try { if(dragStart < 0) drag_range = new MarkerRange (entryGroup.getSequenceEntry().getBases().getForwardStrand(), start, start); else drag_range = new MarkerRange (entryGroup.getSequenceEntry().getBases().getForwardStrand(), dragStart, start); getSelection().setMarkerRange(drag_range); repaint(); } catch (OutOfRangeException e) { e.printStackTrace(); } } private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos) { int markStart = (Math.round(start/division)*division); if(markStart < 1) markStart = 1; int sm = markStart-(division/2); float x; if(sm > start) { x = (sm-start)*pixPerBase; g2.drawLine((int)x, ypos-14,(int)x, ypos-12); } for(int m=markStart; m 1) { getSelection().clear(); repaint(); } } public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { dragStart = -1; maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if(e.isPopupTrigger()) { if(showDetails != null) { popup.remove(showDetails); popup.remove(annotate); } mouseVCF = null; findVariantAtPoint(e.getPoint()); final VCFRecord thisMouseVCF = mouseVCF; final int thisMouseOverIndex = mouseOverIndex; if( thisMouseVCF != null ) { showDetails = new JMenuItem("Show details of : "+ thisMouseVCF.getChrom()+":"+thisMouseVCF.getPos()+" "+thisMouseVCF.getID()); showDetails.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { FileViewer viewDetail = new FileViewer( thisMouseVCF.getChrom()+":"+thisMouseVCF.getPos()+" "+thisMouseVCF.getID(), true, false, true); viewDetail.appendString(vcfReaders[mouseOverIndex].getHeader()+"\n", Level.INFO); viewDetail.appendString("Seq : "+thisMouseVCF.getChrom()+"\n", Level.DEBUG); viewDetail.appendString("Pos : "+thisMouseVCF.getPos()+"\n", Level.DEBUG); viewDetail.appendString("ID : "+thisMouseVCF.getID()+"\n", Level.DEBUG); viewDetail.appendString("Ref : "+thisMouseVCF.getRef()+"\n", Level.DEBUG); viewDetail.appendString("Alt : "+thisMouseVCF.getAlt().toString()+"\n", Level.DEBUG); viewDetail.appendString("Qual : "+thisMouseVCF.getQuality()+"\n", Level.DEBUG); viewDetail.appendString("Filter: "+thisMouseVCF.getFilter()+"\n", Level.DEBUG); viewDetail.appendString("Info : "+thisMouseVCF.getInfo()+"\n", Level.DEBUG); if(thisMouseVCF.getFormat() != null) { viewDetail.appendString("\nGenotype information:\n", Level.INFO); viewDetail.appendString("Format: "+thisMouseVCF.getFormat()+"\n", Level.DEBUG); viewDetail.appendString(thisMouseVCF.getSampleDataString()+"\n", Level.DEBUG); } viewDetail.getTextPane().setCaretPosition(0); } }); popup.add(showDetails); annotate = new JMenuItem("Manual PASS / FAIL"); annotate.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new ManualAnnotation(thisMouseVCF, thisMouseOverIndex); } }); popup.add(annotate); } popup.show(e.getComponent(), e.getX(), e.getY()); } } } private void setDisplay() { Dimension d = new Dimension(); if(splitSamples) { int count = 0; for(int i=0; i vcfFileList = new Vector(); String reference = null; if(args.length == 0) { System.setProperty("default_directory", System.getProperty("user.dir")); FileSelectionDialog fileSelection = new FileSelectionDialog( null, true, "VCFview", "VCF"); vcfFileList = fileSelection.getFiles(VCFFILE_SUFFIX); reference = fileSelection.getReferenceFile(); if(reference.equals("")) reference = null; if(vcfFileList == null || vcfFileList.size() < 1) System.exit(0); } else if(!args[0].startsWith("-")) { for(int i=0; i< args.length; i++) vcfFileList.add(args[i]); } int nbasesInView = 5000000; for(int i=0;i