package com.compomics.util.gui.spectrum; import com.compomics.util.experiment.biology.Ion; import com.compomics.util.experiment.biology.ions.PeptideFragmentIon; import com.compomics.util.experiment.identification.matches.IonMatch; import com.compomics.util.experiment.identification.identification_parameters.PtmSettings; import java.awt.event.MouseEvent; import javax.swing.*; import java.awt.*; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; /** * This class was imported from the Peptizer and MascotDatfile parser, and was * developed to display fragmentation information on the modified sequence as * inspired by X!Tandem. * * @author Kenny Helsens * @author Lennart Martens * @author Harald Barsnes */ public class SequenceFragmentationPanel extends JPanel { /** * A map of the rectangles that have tooltips, i.e., the fragment ion peaks * and the PTM highlighting. */ private HashMap<String, Rectangle> tooltipRectangles; /** * Elementary data for composing the Panel. */ private String[] iSequenceComponents; /** * The list of fragment ion matches. */ private ArrayList<IonMatch> iIonMatches; /** * Double array on b-ions for the sequence components. If '0', no * corresponding ions were given for the component. Otherwise, a double * between [0:1] is stored in the array that is relative with the intensity * of the most intense fragment ion. */ private double[] bIons; /** * Double array on y-ions for the sequence components. If '0', no * corresponding ions were given for the component. Otherwise, a double * between [0:1] is stored in the array that is relative with the intensity * of the most intense fragment ion. */ private double[] yIons; /** * The font to use. */ private Font iBaseFont = new Font("Monospaced", Font.PLAIN, 14); /** * The maximum bar height. */ private final double iMaxBarHeight = 40; /** * The width of the bars. */ private final int iBarWidth = 3; /** * The horizontal space. */ private final int iHorizontalSpace = 3; /** * The x-axis start position. */ private final int iXStart = 10; /** * This boolean holds whether or not the given sequence is a modified * sequence or a normal peptide sequence. * * Normal: KENNY Modified: NH2-K<Ace>ENNY-COOH */ private boolean isModifiedSequence; /** * If true the modification are highlighted with a background color. */ private boolean iHighlightModifications; /** * The modification profile. */ private PtmSettings modificationProfile; /** * the forward ion type (for instance B ion) as indexed by the * PeptideFragmentIon static fields */ private int forwardIon; /** * the rewind ion type (for instance B ion) as indexed by the * PeptideFragmentIon static fields */ private int rewindIon; /** * Color for the forward ion */ private Color forwardColor; /** * Color for the rewind ion */ private Color rewindColor; /** * Creates a new SequenceFragmentationPanel. * * @deprecated use the panel with ion selection instead * @param aSequence String with the Modified Sequence of an peptide * identification. * @param aIonMatches ArrayList with Fragmentation ion matches. * @param boolModifiedSequence boolean describing the sequence. This * constructor can be used to enter a ModifiedSequence or a normal sequence. * @param aHighlightModifications boolean decides whether the modification * are highlighted by adding a star above the modified residue instead if * displaying the PTM short name * @param modificationProfile the modification profile * @throws java.awt.HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless * @see javax.swing.JComponent#getDefaultLocale */ public SequenceFragmentationPanel(String aSequence, ArrayList<IonMatch> aIonMatches, boolean boolModifiedSequence, boolean aHighlightModifications, PtmSettings modificationProfile) throws HeadlessException { this(aSequence, aIonMatches, boolModifiedSequence, aHighlightModifications, modificationProfile, PeptideFragmentIon.B_ION, PeptideFragmentIon.Y_ION); } /** * Creates a new SequenceFragmentationPanel working with B and Y ions. * * @param aSequence String with the Modified Sequence of an peptide * identification. * @param aIonMatches ArrayList with Fragmentation ion matches. * @param boolModifiedSequence boolean describing the sequence. This * constructor can be used to enter a ModifiedSequence or a normal sequence. * @param aHighlightModifications boolean decides whether the modification * are highlighted by adding a star above the modified residue instead if * displaying the PTM short name * @param modificationProfile the modification profile * @param forwardIon the forward ion type (for instance B ion) as indexed by * the PeptideFragmentIon static fields * @param rewindIon the rewind ion type (for instance Y ion) as indexed by * the PeptideFragmentIon static fields * @throws java.awt.HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless * @see javax.swing.JComponent#getDefaultLocale */ public SequenceFragmentationPanel(String aSequence, ArrayList<IonMatch> aIonMatches, boolean boolModifiedSequence, boolean aHighlightModifications, PtmSettings modificationProfile, int forwardIon, int rewindIon) throws HeadlessException { super(); this.forwardIon = forwardIon; forwardColor = SpectrumPanel.determineFragmentIonColor(Ion.getGenericIon(Ion.IonType.PEPTIDE_FRAGMENT_ION, forwardIon), false); this.rewindIon = rewindIon; rewindColor = SpectrumPanel.determineFragmentIonColor(Ion.getGenericIon(Ion.IonType.PEPTIDE_FRAGMENT_ION, rewindIon), false); this.modificationProfile = modificationProfile; isModifiedSequence = boolModifiedSequence; iSequenceComponents = parseSequenceIntoComponents(aSequence); iIonMatches = aIonMatches; iHighlightModifications = aHighlightModifications; this.normalizeMatchedIons(); this.setPreferredSize(new Dimension(estimateWidth(), estimateHeight())); tooltipRectangles = new HashMap<String, Rectangle>(); addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent me) { mouseMovedHandler(me); } }); } /** * Creates a new SequenceFragmentationPanel working with B and Y ions. * * @param taggedModifiedSequence the tagged modified peptide sequence * @param aIonMatches ArrayList with Fragmentation ion matches. * @param aHighlightModifications boolean decides whether the modification * are highlighted by adding a star above the modified residue instead if * displaying the PTM short name * @param modificationProfile the modification profile * @param forwardIon the forward ion type (for instance B ion) as indexed by * the PeptideFragmentIon static fields * @param rewindIon the rewind ion type (for instance Y ion) as indexed by * the PeptideFragmentIon static fields * @throws java.awt.HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless * @see javax.swing.JComponent#getDefaultLocale */ public SequenceFragmentationPanel(String taggedModifiedSequence, ArrayList<IonMatch> aIonMatches, boolean aHighlightModifications, PtmSettings modificationProfile, int forwardIon, int rewindIon) throws HeadlessException { super(); this.forwardIon = forwardIon; forwardColor = SpectrumPanel.determineFragmentIonColor(Ion.getGenericIon(Ion.IonType.PEPTIDE_FRAGMENT_ION, forwardIon), false); this.rewindIon = rewindIon; rewindColor = SpectrumPanel.determineFragmentIonColor(Ion.getGenericIon(Ion.IonType.PEPTIDE_FRAGMENT_ION, rewindIon), false); this.modificationProfile = modificationProfile; isModifiedSequence = true; iSequenceComponents = parseSequenceIntoComponents(taggedModifiedSequence); iIonMatches = aIonMatches; iHighlightModifications = aHighlightModifications; this.normalizeMatchedIons(); this.setPreferredSize(new Dimension(estimateWidth(), estimateHeight())); tooltipRectangles = new HashMap<String, Rectangle>(); addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent me) { mouseMovedHandler(me); } }); } /** * Paints the SequenceFragmentationPanel. * * Based on the given ModifiedSequence Components and Fragmentions, a * visualization (inspired by X!Tandem) is drawn on a Graphics object. Next * to every possible fragmentation site of the peptide a bar is drawn whether * b or y ions were found originating from this fragmentation side. * * @param g the specified Graphics window * @see java.awt.Component#update(java.awt.Graphics) */ public void paint(Graphics g) { super.paint(g); Graphics2D g2 = (Graphics2D) g; // Set the base font, monospaced! g2.setFont(iBaseFont); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Drawing offsets. int yLocation = ((int) iMaxBarHeight) + iXStart; int xLocation = iXStart; int lFontHeight = g2.getFontMetrics().getHeight(); Double lMidStringHeight = yLocation - lFontHeight * 0.2; for (int i = 0; i < iSequenceComponents.length; i++) { // reset base color to black. g2.setColor(Color.black); /** * A. Draw the component. -------------------- */ String residue = iSequenceComponents[i]; String modification = ""; // check if it's a modified sequence boolean modified = residue.contains("<"); // remove the modification from the residue if (modified && iHighlightModifications) { modification = residue.substring(residue.indexOf("<"), residue.lastIndexOf(">") + 1); residue = residue.substring(0, residue.indexOf("<")) + residue.substring(residue.lastIndexOf(">") + 1); } // if modified, highlight the modification if highlighting is selected if (modified) { Color color = modificationProfile.getColor(modification); // not that this mapping works on the short name while the proper mapping is on the long name... if (color == null) { color = forwardColor; } g2.setColor(color); String ptmName = modification.substring(1, modification.length() - 1); // remove the start and end tags if (i == 0) { String nTerminal = residue.substring(0, residue.length() - 1); Rectangle tempRectangle = new Rectangle(xLocation - 1 + g2.getFontMetrics().stringWidth(nTerminal), yLocation - (g2.getFontMetrics().getHeight() / 2) - 1, g2.getFontMetrics().stringWidth(residue.substring(residue.length() - 1)) + 2, (g2.getFontMetrics().getHeight() / 2) + 4); g2.fill(tempRectangle); tooltipRectangles.put("<html>" + ptmName + " (" + (i + 1) + ")</html>", tempRectangle); g2.setColor(Color.BLACK); g2.drawString(nTerminal, xLocation, yLocation); g2.setColor(Color.WHITE); g2.drawString(residue.substring(residue.length() - 1), xLocation + g2.getFontMetrics().stringWidth(nTerminal), yLocation); g2.setColor(Color.BLACK); } else if (i == iSequenceComponents.length - 1) { Rectangle tempRectangle = new Rectangle(xLocation - 1, yLocation - (g2.getFontMetrics().getHeight() / 2) - 1, g2.getFontMetrics().stringWidth(residue.substring(0, 1)) + 2, (g2.getFontMetrics().getHeight() / 2) + 4); g2.fill(tempRectangle); tooltipRectangles.put("<html>" + ptmName + " (" + (i + 1) + ")</html>", tempRectangle); g2.setColor(Color.WHITE); g2.drawString(residue.substring(0, 1), xLocation, yLocation); g2.setColor(Color.BLACK); g2.drawString(residue.substring(1), xLocation + g2.getFontMetrics().stringWidth(residue.substring(0, 1)), yLocation); } else { Rectangle tempRectangle = new Rectangle(xLocation - 1, yLocation - (g2.getFontMetrics().getHeight() / 2) - 1, g2.getFontMetrics().stringWidth(residue) + 2, (g2.getFontMetrics().getHeight() / 2) + 4); g2.fill(tempRectangle); tooltipRectangles.put("<html>" + ptmName + " (" + (i + 1) + ")</html>", tempRectangle); g2.setColor(Color.WHITE); g2.drawString(residue, xLocation, yLocation); g2.setColor(Color.BLACK); } } else { // Draw this component. g2.drawString(residue, xLocation, yLocation); } // Move the XLocation forwards with the component's length and the horizontal spacer.. xLocation += g2.getFontMetrics().stringWidth(residue) + iHorizontalSpace; /** * B. Draw the bars. -------------------- */ int lBarHeight; // bIon Bar if (i <= bIons.length - 1) { if (bIons[i] != 0) { lBarHeight = (int) (bIons[i] * iMaxBarHeight); if (lBarHeight < 5) { lBarHeight = 7; } g2.setColor(forwardColor); Rectangle tempRectangle = new Rectangle(xLocation, lMidStringHeight.intValue() + 1, iBarWidth, lBarHeight); g2.fill(tempRectangle); tooltipRectangles.put("<html>" + PeptideFragmentIon.getSubTypeAsString(forwardIon) + "<sub>" + (i + 1) + "</sub></html>", tempRectangle); } } // yIon Bar if (i <= yIons.length - 1) { if (yIons[yIons.length - (i + 1)] != 0) { lBarHeight = (int) (yIons[yIons.length - (i + 1)] * iMaxBarHeight); if (lBarHeight < 5) { lBarHeight = 7; } g2.setColor(rewindColor); // y bar height and y-axis start are somewhat different for yIons. int yBarStart = lMidStringHeight.intValue() - 1 - lBarHeight; Rectangle tempRectangle = new Rectangle(xLocation, yBarStart, iBarWidth, lBarHeight); g2.fill(tempRectangle); tooltipRectangles.put("<html>" + PeptideFragmentIon.getSubTypeAsString(rewindIon) + "<sub>" + (yIons.length - i) + "</sub></html>", tempRectangle); } } // Move the XLocation forwards with the component's length and the horizontal spacer.. xLocation = xLocation + iBarWidth + iHorizontalSpace; } this.setPreferredSize(new Dimension(xLocation, 200)); } /** * This method can parse a modified sequence String into a String[] with * different components. Primitive analog to getModifiedSequenceComponents() * on a peptidehit. * * @param aSequence String with the Modified sequence of a peptideHit. * @return the modified sequence of the peptidehit in a String[]. Example: * The peptide Ace-K<AceD3>ENNYR-COOH will return a String[] with * [0]Ace-K<AceD3> [1]E [2]N [3]N [4]Y [5]R-COOH */ private String[] parseSequenceIntoComponents(String aSequence) { String[] result; if (isModifiedSequence) { // Given sequence is a ModifiedSequence! ArrayList parts = new ArrayList(); String temp = aSequence; int start = 0; if (temp.startsWith("#")) { int nterm = temp.indexOf("#", start + 1); start = temp.indexOf("-", nterm); } else { start = temp.indexOf("-"); } start++; String part = temp.substring(0, start).trim(); temp = temp.substring(start).trim(); int endIndex = 1; if (temp.charAt(endIndex) == '<') { endIndex++; while (temp.charAt(endIndex) != '>') { endIndex++; } endIndex++; } part += temp.substring(0, endIndex); temp = temp.substring(endIndex); parts.add(part); while (temp.length() > 0) { start = 0; endIndex = 1; if (temp.charAt(start + endIndex) == '<') { endIndex++; while (temp.charAt(start + endIndex) != '>') { endIndex++; } endIndex++; } if (temp.charAt(start + endIndex) == '-') { endIndex = temp.length(); } part = temp.substring(0, endIndex); temp = temp.substring(endIndex); parts.add(part); } result = new String[parts.size()]; parts.toArray(result); } else { // Given sequence is a flat sequence! result = new String[aSequence.length()]; for (int i = 0; i < result.length; i++) { result[i] = Character.toString(aSequence.charAt(i)); } } return result; } /** * Returns an estimation of the width. * * @return an estimation of the width */ private int estimateWidth() { int lEstimateX = iXStart; ArrayList<String> unmodifiedString = new ArrayList<String>(); // remove the ptms, e.g., <oxidation>, as these will not be shown anyway for (String residue : iSequenceComponents) { // check if it's a modified sequence boolean modified = residue.contains("<"); // remove the modification from the residue if (modified) { residue = residue.substring(0, residue.indexOf("<")) + residue.substring(residue.lastIndexOf(">") + 1); } unmodifiedString.add(residue); } for (String temp : unmodifiedString) { // Move X for a text component. lEstimateX += this.getFontMetrics(iBaseFont).stringWidth(temp) + iHorizontalSpace; // Move the XLocation forwards with the component's length and the horizontal spacer. lEstimateX += iBarWidth + iHorizontalSpace; } lEstimateX += iXStart; return lEstimateX; } /** * Returns an estimation of the height. * * @return an estimation of the height */ private int estimateHeight() { int lEstimateY = 0; lEstimateY += 2 * iXStart; lEstimateY += 1.8 * iMaxBarHeight; return lEstimateY; } /** * Build the normalized intensity indexes for the parts of the modified * sequence that were covered by fragment ions. */ private void normalizeMatchedIons() { // Create Y and B boolean arrays. bIons = new double[iSequenceComponents.length - 1]; yIons = new double[iSequenceComponents.length - 1]; // Dig up the most intense matched ion. double lMaxIntensity = 0.0; for (IonMatch lMatch : iIonMatches) { if (lMaxIntensity < lMatch.peak.intensity) { lMaxIntensity = lMatch.peak.intensity; } } for (IonMatch lMatch : iIonMatches) { if (lMatch.ion.getType() == Ion.IonType.PEPTIDE_FRAGMENT_ION) { double lRatio = lMatch.peak.intensity / lMaxIntensity; PeptideFragmentIon lFragmentIon = (PeptideFragmentIon) lMatch.ion; if (lFragmentIon.getSubType() == rewindIon) { // If array unit is not '0', another ion for this fragmentation site is already found. if (yIons[lFragmentIon.getNumber() - 1] != 0) { // We want to save the most intense. if (yIons[lFragmentIon.getNumber() - 1] > lRatio) { // Reset lRatio to the most intense. lRatio = yIons[lFragmentIon.getNumber() - 1]; } } yIons[lFragmentIon.getNumber() - 1] = lRatio; } else if (lFragmentIon.getSubType() == forwardIon) { if (bIons[lFragmentIon.getNumber() - 1] != 0) { if (bIons[lFragmentIon.getNumber() - 1] > lRatio) { lRatio = bIons[lFragmentIon.getNumber() - 1]; } } bIons[lFragmentIon.getNumber() - 1] = lRatio; } } } } /** * Set the Sequence for the SequenceFragmentationPanel. * * @param lSequence String with peptide sequence. * @param boolModifiedSequence Boolean whether lSequence is a Modified * Sequence "NH2-K<Ace>ENNY-COOH" or a Flat Sequence "KENNY". */ public void setSequence(String lSequence, boolean boolModifiedSequence) { isModifiedSequence = boolModifiedSequence; iSequenceComponents = parseSequenceIntoComponents(lSequence); } /** * Set the ArrayList with FragmentIon matches. The double[] indexing b and y * ion intensities will be recalculated. * * @param lIonMatches ArrayList */ public void setIonMatches(ArrayList lIonMatches) { iIonMatches = lIonMatches; normalizeMatchedIons(); } /** * If the mouse hovers over one of the fragment ion peaks the tooltip is set * to the fragment ion type and number. And if hovering over a modified * residue the modification name is shown. If not the tooltip is set to * null. */ private void mouseMovedHandler(MouseEvent me) { String tooltip = null; Iterator<String> rectangles = tooltipRectangles.keySet().iterator(); boolean matchFound = false; // iterate the rectangles and look for matches while (rectangles.hasNext() && !matchFound) { String key = rectangles.next(); if (tooltipRectangles.get(key).contains(me.getPoint())) { tooltip = key; matchFound = true; } } this.setToolTipText(tooltip); } }